package com.avaya.customer.example.servlet.utils;

import static org.apache.commons.lang.StringUtils.isNotEmpty;

import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.convert.DisabledListDelimiterHandler;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.ConfigurationLogger;
import org.apache.commons.configuration2.reloading.PeriodicReloadingTrigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * The properties configuration. This class does not parse property values.
 * <p>
 * Properties that must be non-empty are accessed via getters. Use opt() for properties that may be empty or commented
 * out.
 */
public class Configuration {

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

    public Configuration(File propertiesFile) {
        builder = new ReloadingFileBasedConfigurationBuilder<>(PropertiesConfiguration.class)
                .configure(new Parameters().properties().setFile(propertiesFile).setThrowExceptionOnMissing(true).setListDelimiterHandler(new DisabledListDelimiterHandler())
                        .setIncludesAllowed(false).setLogger(new ConfigurationLogger(Configuration.class)));
        PeriodicReloadingTrigger trigger = new PeriodicReloadingTrigger(builder.getReloadingController(), null, 30, TimeUnit.SECONDS);
        trigger.start();
    }

    private KeyStore clientKeyStore   = null;
    private KeyStore clientTrustStore = null;

    public KeyStore getClientKeyStore() throws Exception {
        if (clientKeyStore == null) {
            LOG.info("Loading client certificate from {} ...", getKeyStorePath());

            clientKeyStore = KeyStore.getInstance("JKS");
            try (FileInputStream fis = new FileInputStream(getKeyStorePath())) {
                clientKeyStore.load(fis, getKeyStorePassword().toCharArray());
            }
        }
        return clientKeyStore;
    }

    public KeyStore getClientTrustStore() throws Exception {
        if (clientTrustStore == null) {
            LOG.info("Loading server certificate from {} ...", getTrustStorePath());

            clientTrustStore = KeyStore.getInstance("JKS");
            try (FileInputStream fis = new FileInputStream(getTrustStorePath())) {
                clientTrustStore.load(fis, getTrustStorePassword().toCharArray());
            }
        }
        return clientTrustStore;
    }

    public String getAuthorizationHost() throws ExternalException, ConfigurationException {
        return get(PropertyKey.authorizationHost);
    }

    public String getClientId() throws ExternalException, ConfigurationException {
        return get(PropertyKey.clientId);
    }

    private String getKeyStorePath() throws ExternalException, ConfigurationException {
        return Constants.appConfigPath + get(PropertyKey.keyStore);
    }

    public String getKeyStorePassword() throws ExternalException, ConfigurationException {
        return get(PropertyKey.keyStorePassword);
    }

    public String getKeyAlias() throws ExternalException, ConfigurationException {
        return get(PropertyKey.keyAlias);
    }

    private String getTrustStorePath() throws ExternalException, ConfigurationException {
        return Constants.appConfigPath + get(PropertyKey.trustStore);
    }

    private String getTrustStorePassword() throws ExternalException, ConfigurationException {
        return get(PropertyKey.trustStorePassword);
    }

    public String getUsername() throws ExternalException, ConfigurationException {
        return get(PropertyKey.username);
    }

    public String getPassword() throws ExternalException, ConfigurationException {
        return get(PropertyKey.password);
    }

    public String getAuthPort() throws ExternalException, ConfigurationException {
        return get(PropertyKey.authPort);
    }

    public boolean isSaveChatTranscripts() {
        return Boolean.valueOf(opt(PropertyKey.saveChatTranscripts));
    }

    public boolean isSaveMessageTranscripts() {
        return Boolean.valueOf(opt(PropertyKey.saveMessageTranscripts));
    }

    public boolean isSaveEmailTranscripts() {
        return Boolean.valueOf(opt(PropertyKey.saveEmailTranscripts));
    }

    public boolean isDownloadAttachments() {
        return Boolean.valueOf(opt(PropertyKey.downloadAttachments));
    }

    private String getOcpHost() throws ExternalException, ConfigurationException {
        return get(PropertyKey.ocpHost);
    }

    private String getOcpAttachmentPath() throws ExternalException, ConfigurationException {
        return get(PropertyKey.ocpAttachmentPath);
    }

    private boolean isOcpSecure() {
        return Boolean.valueOf(opt(PropertyKey.ocpSecure));
    }

    public String getAttachmentUrl() throws ExternalException, ConfigurationException {
        String protocol = isOcpSecure() ? "https" : "http";
        String host = getOcpHost();
        String path = getOcpAttachmentPath();
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        if (!path.endsWith("/")) {
            path += "/";
        }

        return protocol + "://" + host + path;
    }

    void setSsoToken(String ssoToken) throws ExternalException, ConfigurationException {
        set(PropertyKey.ssoToken, ssoToken);
    }

    /**
     * @returns the property value, or null when the property not set
     */
    public String opt(PropertyKey key) {
        String value;
        try {
            value = builder.getConfiguration().getString(actualKeyFor(key), null);
        } catch (ConfigurationException ce) {
            LOG.error("Error while getting value of [" + key + "]. Return [null]", ce);
            value = null;
        }
        return value;
    }

    // =========================== private ===========================

    /**
     * @throws ExternalException when the property is empty or not set
     * @returns a non-empty property value
     */
    private String get(PropertyKey key) throws ExternalException, ConfigurationException {
        final String value = opt(key);
        if (isNotEmpty(value)) {
            return value;
        } else {
            throw new ExternalException("Property %s %s", key, (value != null ? "is empty" : "not specified"));
        }
    }

    private void set(PropertyKey key, String newValue) throws ConfigurationException {
        builder.getConfiguration().setProperty(actualKeyFor(key), (newValue != null) ? newValue : "");
        builder.save();
        LOG.info("Property {} {}", key, (isNotEmpty(newValue) ? "updated" : "cleared"));
    }

    private String actualKeyFor(PropertyKey key) {
        String actualKey = actualKeys.get(key);
        if (actualKey == null) {
            actualKey = key.name();
        }
        return actualKey;
    }

    private final ReloadingFileBasedConfigurationBuilder<PropertiesConfiguration> builder;

    // PropertyKey-to-KeyInPropertiesFile map.
    // Purpose: to ignore case of keys in the properties file. Otherwise it would complain a lot. "clientID" should be
    // as correct as "clientId".
    // The actual key always equalsIgnoreCase the key.
    private final Map<PropertyKey, String>                                        actualKeys = new EnumMap<>(PropertyKey.class);
}
