// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.spring.cloud.core.implementation.util;

import com.azure.core.management.AzureEnvironment;
import com.azure.spring.cloud.core.properties.AzureProperties;
import com.azure.spring.cloud.core.properties.PasswordlessProperties;
import com.azure.spring.cloud.core.properties.authentication.TokenCredentialProperties;
import com.azure.spring.cloud.core.properties.client.ClientProperties;
import com.azure.spring.cloud.core.properties.profile.AzureProfileProperties;
import com.azure.spring.cloud.core.properties.proxy.ProxyProperties;
import com.azure.spring.cloud.core.properties.retry.RetryProperties;
import com.azure.spring.cloud.core.provider.RetryOptionsProvider;
import org.junit.jupiter.api.Test;

import java.time.Duration;

import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE;
import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.AZURE_CHINA;
import static com.azure.spring.cloud.core.provider.AzureProfileOptionsProvider.CloudType.OTHER;
import static com.azure.spring.cloud.core.provider.RetryOptionsProvider.RetryMode.FIXED;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AzurePasswordlessPropertiesUtilsTest {

    @Test
    void testCopyPropertiesToNewObjectShouldEqual() {
        AzurePropertiesA source = new AzurePropertiesA();
        source.client.setApplicationId("client-application-id-A");
        source.profile.setCloudType(AZURE_CHINA);
        source.profile.setTenantId("profile-tenant-id-A");
        source.profile.setSubscriptionId("profile-sub-id-A");
        source.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-A");
        source.proxy.setType("proxy-type-A");
        source.proxy.setHostname("proxy-hostname-A");
        source.proxy.setPort(1234);
        source.proxy.setUsername("proxy-username-A");
        source.proxy.setPassword("proxy-password-A");
        source.retry.setMode(FIXED);
        source.retry.getExponential().setMaxRetries(3);
        source.retry.getExponential().setBaseDelay(Duration.ofSeconds(4));
        source.retry.getExponential().setMaxDelay(Duration.ofSeconds(5));
        source.retry.getFixed().setDelay(Duration.ofSeconds(6));
        source.retry.getFixed().setMaxRetries(7);
        source.credential.setClientId("credential-client-id-A");
        source.credential.setClientSecret("credential-client-secret-A");
        source.credential.setClientCertificatePath("credential-client-cert-path-A");
        source.credential.setClientCertificatePassword("credential-client-cert-password-A");
        source.credential.setUsername("credential-username-A");
        source.credential.setPassword("credential-password-A");
        source.credential.setManagedIdentityEnabled(true);

        final PasswordlessPropertiesB target = new PasswordlessPropertiesB();
        AzurePasswordlessPropertiesUtils.copyAzureCommonProperties(source, target);

        assertEquals(AZURE_CHINA, target.profile.getCloudType());
        assertEquals("profile-tenant-id-A", target.profile.getTenantId());
        assertEquals("profile-sub-id-A", target.profile.getSubscriptionId());
        assertEquals("aad-endpoint-A", target.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-A", target.credential.getClientId());
        assertEquals("credential-client-secret-A", target.credential.getClientSecret());
        assertEquals("credential-client-cert-path-A", target.credential.getClientCertificatePath());
        assertEquals("credential-client-cert-password-A", target.credential.getClientCertificatePassword());
        assertEquals("credential-username-A", target.credential.getUsername());
        assertEquals("credential-password-A", target.credential.getPassword());

    }

    @Test
    void testCopyPropertiesToObjectWithSameFieldsSetShouldOverrideWithNull() {
        PasswordlessPropertiesB target = new PasswordlessPropertiesB();
        target.profile.setCloudType(AZURE);
        target.profile.setTenantId("profile-tenant-id-B");
        target.profile.setSubscriptionId("profile-sub-id-B");
        target.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-B");
        target.credential.setClientId("credential-client-id-B");
        target.credential.setClientSecret("credential-client-secret-B");
        target.credential.setClientCertificatePath("credential-client-cert-path-B");
        target.credential.setClientCertificatePassword("credential-client-cert-password-B");
        target.credential.setUsername("credential-username-B");
        target.credential.setPassword("credential-password-B");
        target.credential.setManagedIdentityEnabled(true);

        // assert properties have been set correctly to target
        assertEquals(AZURE, target.profile.getCloudType());
        assertEquals("profile-tenant-id-B", target.profile.getTenantId());
        assertEquals("profile-sub-id-B", target.profile.getSubscriptionId());
        assertEquals("aad-endpoint-B", target.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-B", target.credential.getClientId());
        assertEquals("credential-client-secret-B", target.credential.getClientSecret());
        assertEquals("credential-client-cert-path-B", target.credential.getClientCertificatePath());
        assertEquals("credential-client-cert-password-B", target.credential.getClientCertificatePassword());
        assertEquals("credential-username-B", target.credential.getUsername());
        assertEquals("credential-password-B", target.credential.getPassword());

        AzurePropertiesA source = new AzurePropertiesA();
        source.client.setApplicationId("client-application-id-A");
        source.profile.setCloudType(AZURE_CHINA);
        source.profile.setTenantId("profile-tenant-id-A");
        source.proxy.setHostname("proxy-hostname-A");
        source.retry.getExponential().setMaxRetries(13);
        source.retry.getExponential().setMaxDelay(Duration.ofSeconds(14));
        source.credential.setClientId("credential-client-id-A");

        AzurePasswordlessPropertiesUtils.copyAzureCommonProperties(source, target);

        // assert properties have been set correctly to target after copy
        assertEquals(AZURE_CHINA, target.profile.getCloudType());
        assertEquals("profile-tenant-id-A", target.profile.getTenantId());
        assertNull(target.profile.getSubscriptionId());
        assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryEndpoint(), target.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-A", target.credential.getClientId());
        assertNull(target.credential.getClientSecret());
        assertNull(target.credential.getClientCertificatePath());
        assertNull(target.credential.getClientCertificatePassword());
        assertNull(target.credential.getUsername());
        assertNull(target.credential.getPassword());
    }

    @Test
    void testCopyPropertiesIgnoresNullToObjectWithSameFieldsSetShouldOverrideWithoutNull() {
        PasswordlessPropertiesB target = new PasswordlessPropertiesB();
        target.profile.setCloudType(AZURE);
        target.profile.setTenantId("profile-tenant-id-B");
        target.profile.setSubscriptionId("profile-sub-id-B");
        target.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-B");
        target.credential.setClientId("credential-client-id-B");
        target.credential.setClientSecret("credential-client-secret-B");
        target.credential.setClientCertificatePath("credential-client-cert-path-B");
        target.credential.setClientCertificatePassword("credential-client-cert-password-B");
        target.credential.setUsername("credential-username-B");
        target.credential.setPassword("credential-password-B");
        target.credential.setManagedIdentityEnabled(true);

        // assert properties have been set correctly to target
        assertEquals(AZURE, target.profile.getCloudType());
        assertEquals("profile-tenant-id-B", target.profile.getTenantId());
        assertEquals("profile-sub-id-B", target.profile.getSubscriptionId());
        assertEquals("aad-endpoint-B", target.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-B", target.credential.getClientId());
        assertEquals("credential-client-secret-B", target.credential.getClientSecret());
        assertEquals("credential-client-cert-path-B", target.credential.getClientCertificatePath());
        assertEquals("credential-client-cert-password-B", target.credential.getClientCertificatePassword());
        assertEquals("credential-username-B", target.credential.getUsername());
        assertEquals("credential-password-B", target.credential.getPassword());

        PasswordlessPropertiesB source = new PasswordlessPropertiesB();
        source.profile.setCloudType(AZURE_CHINA);
        source.profile.setTenantId("profile-tenant-id-A");
        source.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-A");
        source.credential.setClientId("credential-client-id-A");
        source.setPasswordlessEnabled(true);
        source.setScopes("fake-scopes");

        AzurePasswordlessPropertiesUtils.copyAzureCommonPropertiesIgnoreNull(source, target);

        // assert properties have been set correctly to target after copy
        assertEquals(AZURE_CHINA, target.profile.getCloudType());
        assertEquals("profile-tenant-id-A", target.profile.getTenantId());
        assertEquals("profile-sub-id-B", target.profile.getSubscriptionId());
        assertEquals("aad-endpoint-A", target.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), target.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-A", target.credential.getClientId());
        assertEquals("credential-client-secret-B", target.credential.getClientSecret());
        assertEquals("credential-client-cert-path-B", target.credential.getClientCertificatePath());
        assertEquals("credential-client-cert-password-B", target.credential.getClientCertificatePassword());
        assertEquals("credential-username-B", target.credential.getUsername());
        assertEquals("credential-password-B", target.credential.getPassword());
        assertTrue(target.isPasswordlessEnabled());
        assertEquals("fake-scopes", target.getScopes());

    }

    @Test
    void testMergePropertiesObjectWithSameFieldsSetShouldTakeLater() {
        AzurePropertiesA defaultProperties = new AzurePropertiesA();
        defaultProperties.client.setApplicationId("client-application-id-B");
        defaultProperties.profile.setCloudType(AZURE);
        defaultProperties.profile.setTenantId("profile-tenant-id-B");
        defaultProperties.profile.setSubscriptionId("profile-sub-id-B");
        defaultProperties.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-B");
        defaultProperties.proxy.setType("proxy-type-B");
        defaultProperties.proxy.setHostname("proxy-hostname-B");
        defaultProperties.proxy.setPort(1234);
        defaultProperties.proxy.setUsername("proxy-username-B");
        defaultProperties.proxy.setPassword("proxy-password-B");
        defaultProperties.retry.setMode(FIXED);
        defaultProperties.retry.getExponential().setMaxRetries(3);
        defaultProperties.retry.getExponential().setBaseDelay(Duration.ofSeconds(4));
        defaultProperties.retry.getExponential().setMaxDelay(Duration.ofSeconds(5));
        defaultProperties.retry.getFixed().setDelay(Duration.ofSeconds(6));
        defaultProperties.retry.getFixed().setMaxRetries(7);
        defaultProperties.credential.setClientId("credential-client-id-B");
        defaultProperties.credential.setClientSecret("credential-client-secret-B");
        defaultProperties.credential.setClientCertificatePath("credential-client-cert-path-B");
        defaultProperties.credential.setClientCertificatePassword("credential-client-cert-password-B");
        defaultProperties.credential.setUsername("credential-username-B");
        defaultProperties.credential.setPassword("credential-password-B");
        defaultProperties.credential.setManagedIdentityEnabled(true);

        // assert properties have been set correctly to defaultProperties
        assertEquals(AZURE, defaultProperties.profile.getCloudType());
        assertEquals("profile-tenant-id-B", defaultProperties.profile.getTenantId());
        assertEquals("profile-sub-id-B", defaultProperties.profile.getSubscriptionId());
        assertEquals("aad-endpoint-B", defaultProperties.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryGraphApiVersion(), defaultProperties.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-B", defaultProperties.credential.getClientId());
        assertEquals("credential-client-secret-B", defaultProperties.credential.getClientSecret());
        assertEquals("credential-client-cert-path-B", defaultProperties.credential.getClientCertificatePath());
        assertEquals("credential-client-cert-password-B", defaultProperties.credential.getClientCertificatePassword());
        assertEquals("credential-username-B", defaultProperties.credential.getUsername());
        assertEquals("credential-password-B", defaultProperties.credential.getPassword());

        PasswordlessPropertiesB propertiesToOverride = new PasswordlessPropertiesB();
        propertiesToOverride.profile.setCloudType(AZURE_CHINA);
        propertiesToOverride.profile.setTenantId("profile-tenant-id-A");
        propertiesToOverride.profile.getEnvironment().setActiveDirectoryEndpoint("aad-endpoint-A");
        propertiesToOverride.credential.setClientId("credential-client-id-A");
        propertiesToOverride.setScopes("fake-scopes-1");

        PasswordlessPropertiesB result = new PasswordlessPropertiesB();
        AzurePasswordlessPropertiesUtils.mergeAzureCommonProperties(defaultProperties, propertiesToOverride, result);

        // assert properties have been set correctly to result after copy
        assertEquals(AZURE_CHINA, result.profile.getCloudType());
        assertEquals("profile-tenant-id-A", result.profile.getTenantId());
        assertEquals("profile-sub-id-B", result.profile.getSubscriptionId());
        assertEquals("aad-endpoint-A", result.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals(AzureEnvironment.AZURE_CHINA.getActiveDirectoryGraphApiVersion(), result.profile.getEnvironment().getActiveDirectoryGraphApiVersion());
        assertEquals("credential-client-id-A", result.credential.getClientId());
        assertEquals("credential-client-secret-B", result.credential.getClientSecret());
        assertEquals("credential-client-cert-path-B", result.credential.getClientCertificatePath());
        assertEquals("credential-client-cert-password-B", result.credential.getClientCertificatePassword());
        assertEquals("credential-username-B", result.credential.getUsername());
        assertEquals("credential-password-B", result.credential.getPassword());
        assertEquals("fake-scopes-1", result.getScopes());
        assertFalse(result.isPasswordlessEnabled());
    }

    @Test
    void testCopyPropertiesIgnoreNullToObjectWithDifferentFieldsSetShouldMerge() {
        PasswordlessPropertiesB source = new PasswordlessPropertiesB();
        source.credential.setClientId("client-id-A");
        source.getProfile().setCloudType(AZURE);
        source.setScopes("fake-scopes");
        source.setPasswordlessEnabled(true);

        PasswordlessPropertiesB target = new PasswordlessPropertiesB();
        target.credential.setClientSecret("client-secret-B");
        target.profile.setCloudType(OTHER);
        target.profile.getEnvironment().setActiveDirectoryEndpoint("abc");

        assertTrue(source.isPasswordlessEnabled());
        assertFalse(target.isPasswordlessEnabled());
        assertEquals("fake-scopes", source.getScopes());
        assertNull(target.getScopes());

        assertEquals(AZURE, source.getProfile().getCloudType());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(),
            source.profile.getEnvironment().getActiveDirectoryEndpoint());
        assertEquals("client-secret-B", target.credential.getClientSecret());

        AzurePasswordlessPropertiesUtils.copyAzureCommonPropertiesIgnoreNull(source, target);

        // target properties should be merged properties from source + target
        assertEquals("client-id-A", target.credential.getClientId());
        assertEquals("client-secret-B", target.credential.getClientSecret());
        assertEquals(AZURE, source.getProfile().getCloudType());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(),
            target.profile.getEnvironment().getActiveDirectoryEndpoint());


        // source properties should not be updated
        assertNull(source.credential.getClientSecret());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(),
            source.profile.getEnvironment().getActiveDirectoryEndpoint());

        assertTrue(source.isPasswordlessEnabled());
        assertTrue(target.isPasswordlessEnabled());
        assertEquals("fake-scopes", source.getScopes());
        assertEquals("fake-scopes", target.getScopes());
    }

    @Test
    void testCopyPropertiesSourceNotChanged() {
        AzurePropertiesA source = new AzurePropertiesA();
        source.credential.setClientId("client-id-A");
        source.getProfile().setCloudType(AZURE);

        PasswordlessPropertiesB target = new PasswordlessPropertiesB();

        AzurePasswordlessPropertiesUtils.copyAzureCommonProperties(source, target);

        assertEquals("client-id-A", target.credential.getClientId());

        // Update target will not affect source
        target.profile.setCloudType(OTHER);
        target.profile.getEnvironment().setActiveDirectoryEndpoint("abc");

        assertNull(source.retry.getExponential().getBaseDelay());
        assertEquals(AzureEnvironment.AZURE.getActiveDirectoryEndpoint(),
            source.profile.getEnvironment().getActiveDirectoryEndpoint());
    }


    static class AzurePropertiesA implements AzureProperties, RetryOptionsProvider {

        private final ClientProperties client = new ClientProperties();
        private final ProxyProperties proxy = new ProxyProperties();
        private final RetryProperties retry = new RetryProperties();
        private final TokenCredentialProperties credential = new TokenCredentialProperties();
        private final AzureProfileProperties profile = new AzureProfileProperties();

        @Override
        public ClientProperties getClient() {
            return client;
        }

        @Override
        public ProxyProperties getProxy() {
            return proxy;
        }

        @Override
        public RetryProperties getRetry() {
            return retry;
        }

        @Override
        public TokenCredentialProperties getCredential() {
            return credential;
        }

        @Override
        public AzureProfileProperties getProfile() {
            return profile;
        }
    }

    static class PasswordlessPropertiesB implements PasswordlessProperties {

        private final TokenCredentialProperties credential = new TokenCredentialProperties();
        private final AzureProfileProperties profile = new AzureProfileProperties();
        private String scopes;
        private boolean passwordlessEnabled = false;

        @Override
        public TokenCredentialProperties getCredential() {
            return credential;
        }

        @Override
        public AzureProfileProperties getProfile() {
            return profile;
        }

        @Override
        public String getScopes() {
            return scopes;
        }

        @Override
        public void setScopes(String scopes) {
            this.scopes = scopes;
        }

        @Override
        public boolean isPasswordlessEnabled() {
            return passwordlessEnabled;
        }

        @Override
        public void setPasswordlessEnabled(boolean passwordlessEnabled) {
            this.passwordlessEnabled = passwordlessEnabled;
        }
    }
}
