/*
 * Copyright (c) Smals
 */
package be.ehealth.technicalconnector.config;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import be.ehealth.technicalconnector.config.test.ConfigurationModuleTester;
import be.ehealth.technicalconnector.exception.ConfigurationException;
import be.ehealth.technicalconnector.exception.TechnicalConnectorException;
import be.ehealth.technicalconnector.utils.ConnectorIOUtils;
import be.fgov.ehealth.technicalconnector.tests.utils.LoggingUtils;


/**
 * tests the workings of the configFactory.
 *
 * @author EHP
 */
public class ConfigFactoryTest {

    private static final Logger LOG = LoggerFactory.getLogger(ConfigFactoryTest.class);

    private static final String CORRECT_FILE_NAME = "/props/testPropertyFileCorrect.properties";

    private static final String EMPTY_FILE_NAME = "/props/emptyPropertyFile.properties";

    private static final String CHANGED_FILE_NAME = "/props/changedPropertyFile.properties";

    private static final String PROP_SIMILAR_PROD = "/props/propertyFileSimilarToProduction.properties";

    private static final String PROPERTY_KEY_1 = "property.first";

    private static final String PROPERTY_KEY_2 = "property.second";

    private static final String PROPERTY_KEY_3 = "property.third";

    // values in correct file
    private static final String MODULE_PROPERTY_DEFAULT = "originalModuleName";

    private static final String FIRST_VALUE_DEFAULT = "first";

    private static final String SECOND_VALUE_DEFAULT = "second";

    private static final String THIRD_VALUE_DEFAULT = "third";

    // values in changed file
    private static final String MODULE_PROPERTY_CHANGED = "changedModuleName";

    private static final String FIRST_VALUE_CHANGED = "first_changed";

    private static final String SECOND_VALUE_CHANGED = "second_changed";

    private static final String THIRD_VALUE_CHANGED = "third_changed";

    @Before
    @After
    public void resetTest() {
        LoggingUtils.bootstrap();
        ConfigFactory.invalidate();
    }

    @Test
    public void loadLocation() throws Exception {
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        assertProperties("load of correct file , all properties should have those values", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator());
    }

    @Test
    public void loadAsStream() throws Exception {
        InputStream is = ConnectorIOUtils.getResourceAsStream(CORRECT_FILE_NAME);
        ConfigFactory.setLocation(is);
        assertProperties("load of correct file , all properties should have those values", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator());
    }

    @Test
    public void invalidateCache() throws Exception {
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        assertProperties("load of correct file , all properties should have those values", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator());
        ConfigFactory.getConfigValidator().invalidateCache();
        assertProperties("load after invalidate cache", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator());
    }


    @Test
    public void updateProperty() throws Exception {
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        assertProperties("check after load correct file", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator());
        ConfigFactory.getConfigValidator(getAllTestProperties()).setProperty(PROPERTY_KEY_1, FIRST_VALUE_CHANGED);
        assertProperties("check retrieval of changed property with different configValidator", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_CHANGED, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator());
        assertProperties("check retrieval of changed property with different configValidator", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_CHANGED, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidatorFor(PROPERTY_KEY_1));
        assertProperties("check retrieval of changed property with different configValidator", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_CHANGED, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator(Arrays.asList(PROPERTY_KEY_1, PROPERTY_KEY_2)));
    }

    @Test
    public void getDefaultValue() throws Exception {
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        ConfigValidator configValidator = ConfigFactory.getConfigValidator(getAllTestProperties());
        Assert.assertEquals("value should not have default value if property is present", FIRST_VALUE_DEFAULT, configValidator.getProperty(PROPERTY_KEY_1, "default"));
        Assert.assertEquals("value should not have default value if property is present", "default", configValidator.getProperty("nonExistingProperty", "default"));
    }

    private static List<String> getAllTestProperties() {
        ArrayList<String> expectedValues = new ArrayList<String>();
        expectedValues.add(ConfigurationModuleTester.TEST_KEY);
        expectedValues.add(PROPERTY_KEY_1);
        expectedValues.add(PROPERTY_KEY_2);
        expectedValues.add(PROPERTY_KEY_3);
        return expectedValues;
    }

    @Test
    public void loadEmptyFileConfigValidatorShowsInvalidTest() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator configValidator = ConfigFactory.getConfigValidator(getAllTestProperties());
        Assert.assertFalse(configValidator.isValid());
        List<Object> unfoundPropertiesAfterValidation = configValidator.getUnfoundPropertiesAfterValidation();
        for (String expectedKey : getAllTestProperties()) {
            Assert.assertTrue(unfoundPropertiesAfterValidation.contains(expectedKey));
        }
    }

    @Test
    public void reloadWithSameConfigValidatorObjectTest() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator configValidator = ConfigFactory.getConfigValidator();
        assertProperties("empty file should all empty properties", null, null, null, null, configValidator);
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        assertProperties("check after load correct file", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, configValidator);
        assertProperties("check after load correct file", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidator(getAllTestProperties()));
        String[] requiredTestPropertiesAsArray = getAllTestProperties().toArray(new String[]{});
        assertProperties("check retrieval of changed property with different configValidator", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, ConfigFactory.getConfigValidatorFor(requiredTestPropertiesAsArray));

    }

    @Test(expected = ConfigurationException.class)
    public void configValidatorIsInvalidAfterReloadWithEmptyValuesTest() throws Exception {
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        ConfigValidator configValidator = ConfigFactory.getConfigValidator(getAllTestProperties());
        Assert.assertTrue(configValidator.isValid());
        assertProperties("check after load correct file", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, configValidator);
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        assertProperties("empty file should all empty properties", null, null, null, null, configValidator);
        Assert.assertFalse(configValidator.isValid());
        List<Object> unfoundPropertiesAfterValidation = configValidator.getUnfoundPropertiesAfterValidation();
        for (String expectedKey : getAllTestProperties()) {
            Assert.assertTrue(unfoundPropertiesAfterValidation.contains(expectedKey));
        }
    }


    @Test
    public void reloadEmptyAndProgrammaticalyAddPropertiesTest() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);

        ConfigFactory.getConfigValidator().setProperty(PROPERTY_KEY_1, FIRST_VALUE_CHANGED);
        ConfigFactory.getConfigValidator().setProperty(PROPERTY_KEY_2, SECOND_VALUE_CHANGED);
        ConfigFactory.getConfigValidator().setProperty(PROPERTY_KEY_3, THIRD_VALUE_CHANGED);
        ConfigFactory.getConfigValidator().setProperty(ConfigurationModuleTester.TEST_KEY, MODULE_PROPERTY_CHANGED);

        assertProperties("the inserted values should be retrieved without error", MODULE_PROPERTY_CHANGED, FIRST_VALUE_CHANGED, SECOND_VALUE_CHANGED, THIRD_VALUE_CHANGED, ConfigFactory.getConfigValidator());
    }

    @Test
    public void updateProperties() throws Exception {
        ConfigFactory.setConfigLocation(CORRECT_FILE_NAME);
        ConfigValidator configValidator = ConfigFactory.getConfigValidator(getAllTestProperties());
        assertProperties("check after load correct file", MODULE_PROPERTY_DEFAULT, FIRST_VALUE_DEFAULT, SECOND_VALUE_DEFAULT, THIRD_VALUE_DEFAULT, configValidator);

        ConfigFactory.getConfigValidator().setProperty(PROPERTY_KEY_1, FIRST_VALUE_CHANGED);
        ConfigFactory.getConfigValidator().setProperty(PROPERTY_KEY_2, SECOND_VALUE_CHANGED);
        ConfigFactory.getConfigValidator().setProperty(PROPERTY_KEY_3, THIRD_VALUE_CHANGED);
        ConfigFactory.getConfigValidator().setProperty(ConfigurationModuleTester.TEST_KEY, MODULE_PROPERTY_CHANGED);

        assertProperties("the inserted values should be retrieved without error", MODULE_PROPERTY_CHANGED, FIRST_VALUE_CHANGED, SECOND_VALUE_CHANGED, THIRD_VALUE_CHANGED, configValidator);

    }

    @Test(expected = IllegalArgumentException.class)
    public void getConfigValidatorForWithNullValue() throws Exception {
        ConfigFactory.getConfigValidatorFor((String[]) null);
    }

    @Test
    public void getConfigValidatorForWithoutValue() throws Exception {
        ConfigValidator validator = ConfigFactory.getConfigValidatorFor();
        Assert.assertNotNull(validator);
    }

    @Test
    public void endpointTrimming() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigFactory.getConfigValidator().setProperty("endpoint.whitespace", "http://www.ehealth.fgov.be/w ");
        Assert.assertEquals("http://www.ehealth.fgov.be/w", ConfigFactory.getConfigValidator().getProperty("endpoint.whitespace"));
    }

    @Test
    public void recursiveLookup() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "${" + PROPERTY_KEY_2 + "}");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        Assert.assertEquals(SECOND_VALUE_DEFAULT, config.getProperty(PROPERTY_KEY_1));
    }

    @Test(expected = ConfigurationException.class)
    public void loopDetection() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "${" + PROPERTY_KEY_2 + "}");
        config.setProperty(PROPERTY_KEY_2, "${" + PROPERTY_KEY_3 + "}");
        config.setProperty(PROPERTY_KEY_3, "${" + PROPERTY_KEY_1 + "}");
        Assert.assertNotNull(config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void composedRecursiveLookup01() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "${" + PROPERTY_KEY_2 + "}-${" + PROPERTY_KEY_3 + "}");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        config.setProperty(PROPERTY_KEY_3, THIRD_VALUE_DEFAULT);
        Assert.assertEquals(SECOND_VALUE_DEFAULT + "-" + THIRD_VALUE_DEFAULT, config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void composedRecursiveLookup02() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "${" + PROPERTY_KEY_2 + "}-${" + PROPERTY_KEY_2 + "}");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        Assert.assertEquals(SECOND_VALUE_DEFAULT + "-" + SECOND_VALUE_DEFAULT, config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void valueBeforeRecursiveLookup() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "test-${" + PROPERTY_KEY_2 + "}");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        Assert.assertEquals("test-" + SECOND_VALUE_DEFAULT, config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void valueAfterRecursiveLookup() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "${" + PROPERTY_KEY_2 + "}-test");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        Assert.assertEquals(SECOND_VALUE_DEFAULT + "-test", config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void valueAroundRecursiveLookup() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "test-${" + PROPERTY_KEY_2 + "}-test");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        Assert.assertEquals("test-" + SECOND_VALUE_DEFAULT + "-test", config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void lookupSystemProperties() throws Exception {
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "$system{java.io.temp}");
        Assert.assertEquals(System.getProperty("java.io.temp"), config.getProperty(PROPERTY_KEY_1));
    }

    @Test
    public void lookupMixedProperties() throws Exception {
        System.setProperty("system." + PROPERTY_KEY_1, "${" + PROPERTY_KEY_2 + "}");
        ConfigFactory.setConfigLocation(EMPTY_FILE_NAME);
        ConfigValidator config = ConfigFactory.getConfigValidator();
        config.setProperty(PROPERTY_KEY_1, "$system{system." + PROPERTY_KEY_1 + "}");
        config.setProperty(PROPERTY_KEY_2, SECOND_VALUE_DEFAULT);
        Assert.assertEquals(SECOND_VALUE_DEFAULT, config.getProperty(PROPERTY_KEY_1));
    }


    @Test(expected = TechnicalConnectorException.class)
    public void loadMissingFileFromFileSystem() throws Exception {
        ConfigFactory.setConfigLocation("/wrongLocation");
    }

    @Test(expected = TechnicalConnectorException.class)
    public void loadMissingFileFromFileSystemAbsolutePath() throws Exception {
        ConfigFactory.setConfigLocation("c:\temp/wrongLocation");
    }

    @Test(expected = TechnicalConnectorException.class)
    public void loadMissingFileFromFileSystemJustFileName() throws Exception {
        ConfigFactory.setConfigLocation("wrongLocation");
    }

    private static void assertProperties(String errorMessage, String moduleProperty, String firstValue, String secondValue, String thirdValue, ConfigValidator configValidator) {
        Assert.assertEquals("difference in first property " + errorMessage, firstValue, configValidator.getProperty(PROPERTY_KEY_1));
        Assert.assertEquals("difference in second property " + errorMessage, secondValue, configValidator.getProperty(PROPERTY_KEY_2));
        Assert.assertEquals("difference in third property " + errorMessage, thirdValue, configValidator.getProperty(PROPERTY_KEY_3));
        Assert.assertEquals("difference in moduleProperty " + errorMessage, moduleProperty, configValidator.getProperty(ConfigurationModuleTester.TEST_KEY));
    }

}
