package com.netflix.discovery;

import javax.annotation.Nullable;
import java.util.List;
import java.util.UUID;

import com.google.inject.util.Providers;
import com.netflix.appinfo.AmazonInfo;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.MyDataCenterInstanceConfig;
import com.netflix.config.ConfigurationManager;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.resolver.ResolverUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;

/**
 * @author Nitesh Kant
 */
public class BackUpRegistryTest {


    public static final String ALL_REGIONS_VIP_ADDR = "myvip";
    public static final String REMOTE_REGION_INSTANCE_1_HOSTNAME = "blah";
    public static final String REMOTE_REGION_INSTANCE_2_HOSTNAME = "blah2";

    public static final String LOCAL_REGION_APP_NAME = "MYAPP_LOC";
    public static final String LOCAL_REGION_INSTANCE_1_HOSTNAME = "blahloc";
    public static final String LOCAL_REGION_INSTANCE_2_HOSTNAME = "blahloc2";

    public static final String REMOTE_REGION_APP_NAME = "MYAPP";
    public static final String REMOTE_REGION = "myregion";
    public static final String REMOTE_ZONE = "myzone";
    public static final int CLIENT_REFRESH_RATE = 10;
    public static final int NOT_AVAILABLE_EUREKA_PORT = 756473;

    private EurekaClient client;
    private MockBackupRegistry backupRegistry;

    public void setUp(boolean enableRemote) throws Exception {
        ConfigurationManager.getConfigInstance().setProperty("eureka.client.refresh.interval", CLIENT_REFRESH_RATE);
        ConfigurationManager.getConfigInstance().setProperty("eureka.registration.enabled", "false");
        if (enableRemote) {
            ConfigurationManager.getConfigInstance().setProperty("eureka.fetchRemoteRegionsRegistry", REMOTE_REGION);
        }
        ConfigurationManager.getConfigInstance().setProperty("eureka.myregion.availabilityZones", REMOTE_ZONE);
        ConfigurationManager.getConfigInstance().setProperty("eureka.backupregistry", MockBackupRegistry.class.getName());
        ConfigurationManager.getConfigInstance().setProperty("eureka.serviceUrl.default",
                "http://localhost:" + NOT_AVAILABLE_EUREKA_PORT /*Should always be unavailable*/
                        +
                        MockRemoteEurekaServer.EUREKA_API_BASE_PATH);

        InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
        builder.setIPAddr("10.10.101.00");
        builder.setHostName("Hosttt");
        builder.setAppName("EurekaTestApp-" + UUID.randomUUID());
        builder.setDataCenterInfo(new DataCenterInfo() {
            @Override
            public Name getName() {
                return Name.MyOwn;
            }
        });

        ApplicationInfoManager applicationInfoManager = new ApplicationInfoManager(new MyDataCenterInstanceConfig(), builder.build());

        backupRegistry = new MockBackupRegistry();
        setupBackupMock();
        client = new DiscoveryClient(
                applicationInfoManager,
                new DefaultEurekaClientConfig(),
                null,
                Providers.of((BackupRegistry)backupRegistry),
                ResolverUtils::randomize
        );
    }

    @After
    public void tearDown() throws Exception {
        client.shutdown();
        ConfigurationManager.getConfigInstance().clear();
    }

    @Test
    public void testLocalOnly() throws Exception {
        setUp(false);
        Applications applications = client.getApplications();
        List<Application> registeredApplications = applications.getRegisteredApplications();
        System.out.println("***" + registeredApplications);
        Assert.assertNotNull("Local region apps not found.", registeredApplications);
        Assert.assertEquals("Local apps size not as expected.", 1, registeredApplications.size());
        Assert.assertEquals("Local region apps not present.", LOCAL_REGION_APP_NAME, registeredApplications.get(0).getName());
    }

    @Test
    public void testRemoteEnabledButLocalOnlyQueried() throws Exception {
        setUp(true);
        Applications applications = client.getApplications();
        List<Application> registeredApplications = applications.getRegisteredApplications();
        Assert.assertNotNull("Local region apps not found.", registeredApplications);
        Assert.assertEquals("Local apps size not as expected.", 2, registeredApplications.size()); // Remote region comes with no instances.
        Application localRegionApp = null;
        Application remoteRegionApp = null;
        for (Application registeredApplication : registeredApplications) {
            if (registeredApplication.getName().equals(LOCAL_REGION_APP_NAME)) {
                localRegionApp = registeredApplication;
            } else if (registeredApplication.getName().equals(REMOTE_REGION_APP_NAME)) {
                remoteRegionApp = registeredApplication;
            }
        }
        Assert.assertNotNull("Local region apps not present.", localRegionApp);
        Assert.assertTrue("Remote region instances returned for local query.", null == remoteRegionApp || remoteRegionApp.getInstances().isEmpty());
    }

    @Test
    public void testRemoteEnabledAndQueried() throws Exception {
        setUp(true);
        Applications applications = client.getApplicationsForARegion(REMOTE_REGION);
        List<Application> registeredApplications = applications.getRegisteredApplications();
        Assert.assertNotNull("Remote region apps not found.", registeredApplications);
        Assert.assertEquals("Remote apps size not as expected.", 1, registeredApplications.size());
        Assert.assertEquals("Remote region apps not present.", REMOTE_REGION_APP_NAME, registeredApplications.get(0).getName());
    }

    @Test
    public void testAppsHashCode() throws Exception {
        setUp(true);
        Applications applications = client.getApplications();

        Assert.assertEquals("UP_1_", applications.getAppsHashCode());
    }

    private void setupBackupMock() {
        Application localApp = createLocalApps();
        Applications localApps = new Applications();
        localApps.addApplication(localApp);
        backupRegistry.setLocalRegionApps(localApps);
        Application remoteApp = createRemoteApps();
        Applications remoteApps = new Applications();
        remoteApps.addApplication(remoteApp);
        backupRegistry.getRemoteRegionVsApps().put(REMOTE_REGION, remoteApps);
    }

    private Application createLocalApps() {
        Application myapp = new Application(LOCAL_REGION_APP_NAME);
        InstanceInfo instanceInfo = createLocalInstance(LOCAL_REGION_INSTANCE_1_HOSTNAME);
        myapp.addInstance(instanceInfo);
        return myapp;
    }


    private Application createRemoteApps() {
        Application myapp = new Application(REMOTE_REGION_APP_NAME);
        InstanceInfo instanceInfo = createRemoteInstance(REMOTE_REGION_INSTANCE_1_HOSTNAME);
        myapp.addInstance(instanceInfo);
        return myapp;
    }

    private InstanceInfo createRemoteInstance(String instanceHostName) {
        InstanceInfo.Builder instanceBuilder = InstanceInfo.Builder.newBuilder();
        instanceBuilder.setAppName(REMOTE_REGION_APP_NAME);
        instanceBuilder.setVIPAddress(ALL_REGIONS_VIP_ADDR);
        instanceBuilder.setHostName(instanceHostName);
        instanceBuilder.setIPAddr("10.10.101.1");
        AmazonInfo amazonInfo = getAmazonInfo(REMOTE_ZONE, instanceHostName);
        instanceBuilder.setDataCenterInfo(amazonInfo);
        instanceBuilder.setMetadata(amazonInfo.getMetadata());
        return instanceBuilder.build();
    }

    private InstanceInfo createLocalInstance(String instanceHostName) {
        InstanceInfo.Builder instanceBuilder = InstanceInfo.Builder.newBuilder();
        instanceBuilder.setAppName(LOCAL_REGION_APP_NAME);
        instanceBuilder.setVIPAddress(ALL_REGIONS_VIP_ADDR);
        instanceBuilder.setHostName(instanceHostName);
        instanceBuilder.setIPAddr("10.10.101.1");
        AmazonInfo amazonInfo = getAmazonInfo(null, instanceHostName);
        instanceBuilder.setDataCenterInfo(amazonInfo);
        instanceBuilder.setMetadata(amazonInfo.getMetadata());
        return instanceBuilder.build();
    }

    private AmazonInfo getAmazonInfo(@Nullable String availabilityZone, String instanceHostName) {
        AmazonInfo.Builder azBuilder = AmazonInfo.Builder.newBuilder();
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.availabilityZone, (null == availabilityZone) ? "us-east-1a" : availabilityZone);
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.instanceId, instanceHostName);
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.amiId, "XXX");
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.instanceType, "XXX");
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.localIpv4, "XXX");
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.publicIpv4, "XXX");
        azBuilder.addMetadata(AmazonInfo.MetaDataKey.publicHostname, instanceHostName);
        return azBuilder.build();
    }
}
