package ru.yandex.webmaster3.storage.turbo.service;

import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections.CollectionUtils;
import org.apache.http.HttpStatus;
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.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.joda.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.http.HttpConstants;
import ru.yandex.webmaster3.core.http.WebmasterErrorResponse;
import ru.yandex.webmaster3.core.metrics.externals.AbstractExternalAPIService;
import ru.yandex.webmaster3.core.metrics.externals.ExternalDependencyMethod;
import ru.yandex.webmaster3.core.util.JavaMethodWitness;
import ru.yandex.webmaster3.core.util.RetryUtils;
import ru.yandex.webmaster3.storage.util.JsonDBMapping;

/**
 * Created by Oleg Bazdyrev on 07/05/2018.
 * Сервис для работы с рантаймом турбо (пока только проверка наличия турбо версии страницы)
 */
@Service("turboRuntimeService")
public class TurboRuntimeService extends AbstractExternalAPIService {

    public static final TypeReference<List<TurboCheckUrlInfo>> CHECK_URLS_TYPE_REFERENCE =
            new TypeReference<>() {
            };
    private static final Logger log = LoggerFactory.getLogger(TurboRuntimeService.class);
    private static final String METHOD_CHECK_URL = "/turbo/check_urls";
    private static final String PARAM_URL = "url";
    private static final String HEADER_TURBO_API_CLIENT = "x-yandex-turbo-api-client";
    private static final String HEADER_CLIENT_VALUE = "wmc";
    private final String serviceUrl;
    private CloseableHttpClient httpClient;

    @Autowired
    public TurboRuntimeService(@Value("${webmaster3.storage.turbo.runtimeService.url}") String serviceUrl) {
        this.serviceUrl = serviceUrl;
    }

    public void init() {
        RequestConfig requestConfig = RequestConfig.custom()
                .setSocketTimeout(1000)
                .setConnectTimeout(HttpConstants.DEFAULT_CONNECT_TIMEOUT)
                .build();

        httpClient = HttpClients.custom()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionTimeToLive(30, TimeUnit.SECONDS)
                .build();
    }

    /**
     * Проверка наличия у страницы турбо-версии
     *
     * @param url
     * @return
     */
    @ExternalDependencyMethod("check_urls")
    public boolean hasTurboVersion(URL url) {
        try {
            return trackQuery(new JavaMethodWitness() {
                              }, ALL_ERRORS_INTERNAL, () ->
                            RetryUtils.query(RetryUtils.linearBackoff(3, Duration.standardSeconds(15L)),
                                    () -> hasTurboVersionInternal(url.toExternalForm()))
            );
        } catch (Exception e) {
            log.error("Error when checking url " + url + " for turbo version", e);
            return false;
        }
    }

    @ExternalDependencyMethod("load_urls_batch")
    public Map<String, String> loadTurboUrls(Collection<String> urls) {
        try {
            return trackQuery(new JavaMethodWitness() {
                              }, ALL_ERRORS_INTERNAL, () ->
                            RetryUtils.query(RetryUtils.linearBackoff(3, Duration.standardSeconds(3L)),
                                    () -> loadTurboUrlsInternal(urls))
            );
        } catch (Exception e) {
            log.error("Error when checking url " + urls + " for turbo version", e);
            return null;
        }
    }

    private Map<String, String> loadTurboUrlsInternal(Collection<String> urls) throws Exception {
        log.info("Checking url {} for turbo version", urls);
        URIBuilder uriBuilder = new URIBuilder(serviceUrl);
        uriBuilder.setPath(METHOD_CHECK_URL);
        for (String url : urls) {
            uriBuilder.addParameter(PARAM_URL, url);
        }
        //uriBuilder.addParameter("check_prefixes", "1");

        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(HEADER_TURBO_API_CLIENT, HEADER_CLIENT_VALUE);
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            if (response.getStatusLine().getStatusCode() % 100 == 5) {
                throw new WebmasterException("Internal turbopages service error",
                        new WebmasterErrorResponse.TurboErrorResponse(getClass(), null, "Internal turbopages service error"));
            }

            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                log.error("Turbo runtime service return code is not OK : {}", response.getStatusLine().getStatusCode());
                return null;
            }

            // try to read result
            List<TurboCheckUrlInfo> result =
                    JsonDBMapping.OM.readValue(response.getEntity().getContent(), CHECK_URLS_TYPE_REFERENCE);
            return result.stream().collect(Collectors.toMap(e -> e.originalUrl, e -> e.turboUrl));
        }
    }

    private boolean hasTurboVersionInternal(String url) throws Exception {
        log.info("Checking url {} for turbo version", url);
        URIBuilder uriBuilder = new URIBuilder(serviceUrl);
        uriBuilder.setPath(METHOD_CHECK_URL);
        uriBuilder.addParameter(PARAM_URL, url);
        uriBuilder.addParameter("check_prefixes", "1");

        HttpGet httpGet = new HttpGet(uriBuilder.build());
        httpGet.addHeader(HEADER_TURBO_API_CLIENT, HEADER_CLIENT_VALUE);
        try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
            if (response.getStatusLine().getStatusCode() % 100 == 5) {
                throw new WebmasterException("Internal turbopages service error",
                        new WebmasterErrorResponse.TurboErrorResponse(getClass(), null, "Internal turbopages service error"));
            }

            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                log.error("Turbo runtime service return code is not OK : {}", response.getStatusLine().getStatusCode());
                return false;
            }

            // try to read result
            List<TurboCheckUrlInfo> result =
                    JsonDBMapping.OM.readValue(response.getEntity().getContent(), CHECK_URLS_TYPE_REFERENCE);
            return !CollectionUtils.isEmpty(result);
        }
    }

    @Getter
    @RequiredArgsConstructor(onConstructor_ = {@JsonCreator})
    private static final class TurboCheckUrlInfo {
        @JsonProperty("original_url")
        private final String originalUrl;
        @JsonProperty("turbo_url")
        private final String turboUrl;
    }
}
