package ru.yandex.webmaster3.storage.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URI;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;

import ru.yandex.webmaster3.core.http.HttpConstants;
import ru.yandex.wmtools.common.sita.UserAgentEnum;

/**
 * @author aherman
 */
public class DCNameProvider {
    private static final Logger log = LoggerFactory.getLogger(DCNameProvider.class);

    public static final String DEPLOYMENT_QLOUD_DC = "deployment.qloud.dc";
    public static final String DEPLOYMENT_YADEPLOY_DC = "DEPLOY_NODE_DC";

    private URI conductorGetDCJsonUri;
    private int connectTimeoutMS = HttpConstants.DEFAULT_CONNECT_TIMEOUT;
    private int socketTimeoutMS = 2000;
    private File dcNameCacheFile;

    private String localDC = null;
    private String qloudDcName = null;
    private String yaDeployDcName = null;
    private String fallbackDC = null;

    public void init() {
        this.qloudDcName = StringUtils.trimToNull(System.getProperty(DEPLOYMENT_QLOUD_DC));
        this.yaDeployDcName = StringUtils.trimToNull(System.getenv(DEPLOYMENT_YADEPLOY_DC));

        if (StringUtils.isEmpty(localDC) && !StringUtils.isEmpty(yaDeployDcName)) {
            log.info("Get DC from Ya-deploy: {}", yaDeployDcName);
            this.localDC = yaDeployDcName.toUpperCase();
        }

        if (StringUtils.isEmpty(localDC) && !StringUtils.isEmpty(qloudDcName)) {
            log.info("Get DC from QLOUD: {}", qloudDcName);
            this.localDC = qloudDcName;
        }

        if (StringUtils.isEmpty(localDC) && dcNameCacheFile != null && dcNameCacheFile.exists()) {
            FileReader dcNameReader = null;
            try {
                log.info("Try to read DC name from cache: {}", dcNameCacheFile.getAbsolutePath());
                dcNameReader = new FileReader(dcNameCacheFile);
                List<String> lines = IOUtils.readLines(dcNameReader);
                if (!lines.isEmpty()) {
                    String dc = StringUtils.trimToEmpty(lines.get(0));
                    if (dc.indexOf(' ') >= 0) {
                        log.error("Wrong DC name cache: {}", dc);
                    } else {
                        localDC = dc;
                    }
                }
            } catch (IOException e) {
                log.warn("Unable to read DC name cache file", e);
            } finally {
                IOUtils.closeQuietly(dcNameReader);
            }
        }

        if (StringUtils.isEmpty(localDC)) {
            log.info("Try to get DC name: {}", conductorGetDCJsonUri.toString());
            HttpClientBuilder clientBuilder = HttpClients.custom();
            clientBuilder = clientBuilder.setUserAgent(UserAgentEnum.WEBMASTER.getValue());
            CloseableHttpClient client = clientBuilder.build();
            HttpGet get = new HttpGet(conductorGetDCJsonUri);
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(connectTimeoutMS)
                    .setSocketTimeout(socketTimeoutMS)
                    .build();
            get.setConfig(requestConfig);

            CloseableHttpResponse response = null;
            try {
                response = client.execute(get);
                HttpEntity entity = response.getEntity();
                ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
                entity.writeTo(baos);
                byte[] bytes = baos.toByteArray();
                ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
                ObjectMapper om = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
                ConductorResponse[] conductorResponses = om.readValue(bais, ConductorResponse[].class);
                if (conductorResponses == null || conductorResponses.length == 0) {
                    log.error("Unable to read conductor response: {}", new String(bytes));
                } else {
                    localDC = StringUtils.trimToEmpty(conductorResponses[0].rootDatacenter).toUpperCase();
                }
            } catch (IOException e) {
                log.error("Unable to query conductor for DC name", e);
            } finally {
                IOUtils.closeQuietly(response);
            }
            if (localDC != null && dcNameCacheFile != null) {
                log.info("Save DC name to cache: {}", dcNameCacheFile.getAbsolutePath());
                PrintWriter pw = null;
                try {
                    pw = new PrintWriter(dcNameCacheFile);
                    pw.println(localDC);
                } catch (FileNotFoundException e) {
                    log.warn("Unable to save DC name to cache: {}", dcNameCacheFile.getAbsolutePath());
                } finally {
                    IOUtils.closeQuietly(pw);
                }
            }
        }
        if (StringUtils.isEmpty(localDC) && !StringUtils.isEmpty(fallbackDC)) {
            log.warn("Using fallback DC {}", fallbackDC);
            localDC = fallbackDC;
        }
        log.info("Local DC name: {}", localDC);
    }

    public String getDcName() {
        return localDC;
    }

    public static class ConductorResponse {
        String group;
        String fqdn;
        String datacenter;
        String rootDatacenter;

        public void setGroup(String group) {
            this.group = group;
        }

        public void setFqdn(String fqdn) {
            this.fqdn = fqdn;
        }

        public void setDatacenter(String datacenter) {
            this.datacenter = datacenter;
        }

        @JsonProperty(value = "root_datacenter")
        public void setRootDatacenter(String rootDatacenter) {
            this.rootDatacenter = rootDatacenter;
        }
    }

    public void setFallbackDC(String fallbackDC) {
        this.fallbackDC = fallbackDC;
    }

    @Required
    public void setConductorGetDCJsonUri(URI conductorGetDCJsonUri) {
        this.conductorGetDCJsonUri = conductorGetDCJsonUri;
    }

    public void setDcNameCacheFile(File dcNameCacheFile) {
        this.dcNameCacheFile = dcNameCacheFile;
    }

    public void setQloudDcName(String qloudDcName) {
        this.qloudDcName = qloudDcName;
    }

    public void setConnectTimeoutMS(int connectTimeoutMS) {
        this.connectTimeoutMS = connectTimeoutMS;
    }

    public void setSocketTimeoutMS(int socketTimeoutMS) {
        this.socketTimeoutMS = socketTimeoutMS;
    }
}
