package ru.yandex.webmaster3.core.comment;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
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.methods.HttpPut;
import org.apache.http.config.SocketConfig;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriComponentsBuilder;

import ru.yandex.webmaster3.core.WebmasterException;
import ru.yandex.webmaster3.core.comment.model.LazyInitChatData;
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.security.tvm.TVMTokenService;
import ru.yandex.webmaster3.core.util.JavaMethodWitness;
import ru.yandex.webmaster3.core.util.json.JsonMapping;

/**
 * ishalaru
 * 08.11.2019
 **/
@Service
@Slf4j
public class CmntIntegraionService extends AbstractExternalAPIService {
    private static final int SOCKET_TIMEOUT = 2_000;
    public static final String SUPPLIER_SERVICE_SLUG_UGC = "ugc";
    private static final String SUPPLIER_SERVICE_SLUG_TURBO = "turbo";
    private static final String SUPPLIER_SERVICE_SLUG = "supplierServiceSlug";
    private static final String SIGN_API = "/cmnt/v1/sign";
    private static final String BATCH_INIT_API = "/cmnt/v1/init-batch";
    private static final String CMNT_BATCH_COUNT_API = "/cmnt/v1/brief";
    private static final ObjectMapper OM = new ObjectMapper();
    private final String cmntUri;
    private final TVMTokenService tvmTokenService;


    private CloseableHttpClient httpClient;

    public CmntIntegraionService(@Value("${webmaster3.core.cmnt.host}") String cmntUri,
                                 @Qualifier("cmntTVMTokenService") TVMTokenService tvmTokenService) {
        this.cmntUri = cmntUri;
        this.tvmTokenService = tvmTokenService;
    }

    @PostConstruct
    public void init() {
        SocketConfig socketConfig = SocketConfig.custom()
                .setSoTimeout(SOCKET_TIMEOUT)
                .build();

        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(HttpConstants.DEFAULT_CONNECT_TIMEOUT)
                .setSocketTimeout(SOCKET_TIMEOUT)
                .setConnectionRequestTimeout(HttpConstants.DEFAULT_CONNECTION_REQUEST_TIMEOUT)
                .build();

        httpClient = HttpClientBuilder.create()
                .setDefaultSocketConfig(socketConfig)
                .setMaxConnPerRoute(5)
                .setMaxConnTotal(5)
                .setDefaultRequestConfig(requestConfig)
                .build();
    }

    @PreDestroy
    public void destroy() {
        IOUtils.closeQuietly(httpClient);
    }

    /**
     * External method which return count of comments in thread
     *
     * @return
     */
    @ExternalDependencyMethod("cmnt-batch-count")
    public Map<String, Integer> loadCommentsCount(Collection<String> entitiesId) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(cmntUri + CMNT_BATCH_COUNT_API);
            uriComponentsBuilder.queryParam("entityId", entitiesId.toArray());
            uriComponentsBuilder.queryParam(SUPPLIER_SERVICE_SLUG, SUPPLIER_SERVICE_SLUG_TURBO);
            URI uri = uriComponentsBuilder.build().toUri();
            HttpGet httpRequest = new HttpGet(uri);
            httpRequest.addHeader(TVMTokenService.TVM2_TICKET_HEADER, tvmTokenService.getToken());
            //httpRequest.addHeader(CMNT_API_NAME, cmntApiKey);
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequest)) {
                if (httpResponse.getStatusLine().getStatusCode() / 100 != 2) {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    httpResponse.getEntity().writeTo(byteArrayOutputStream);
                    log.error("Cmnt batch-count code: {}, answer: {}", httpResponse.getStatusLine(), new String(byteArrayOutputStream.toByteArray()));
                    throw new WebmasterException("Cmnt query failed, query: ",
                            new WebmasterErrorResponse.CmntErrorResponse(getClass(), null), null);
                } else {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    httpResponse.getEntity().writeTo(byteArrayOutputStream);
                    final CmntBriefInfo cmntBriefInfo = JsonMapping.OM.readValue(byteArrayOutputStream.toByteArray(), CmntBriefInfo.class);
                    return cmntBriefInfo.feed.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().getCount()));
                }
            } catch (IOException exp) {

                throw new WebmasterException("Cmnt query failed, query: ",
                        new WebmasterErrorResponse.CmntErrorResponse(getClass(), exp), exp);
            }
        });
    }

    @ExternalDependencyMethod("cmnt-batch-init")
    public boolean initChats(final Collection<String> entitiesId) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {

            URI uri = UriComponentsBuilder.fromUriString(cmntUri + BATCH_INIT_API).build().toUri();
            HttpPut httpRequest = new HttpPut(uri);
            StringEntity params = new StringEntity(buildInitBatchBody(entitiesId), Charset.defaultCharset());
            httpRequest.setEntity(params);
            httpRequest.addHeader(TVMTokenService.TVM2_TICKET_HEADER, tvmTokenService.getToken());
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequest)) {
                if (httpResponse.getStatusLine().getStatusCode() / 100 != 2) {
                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                    httpResponse.getEntity().writeTo(byteArrayOutputStream);
                    log.error("Cmnt batch-init code: {}, answer: {}", httpResponse.getStatusLine(), new String(byteArrayOutputStream.toByteArray()));
                    return false;
                }
            } catch (IOException exp) {

                throw new WebmasterException("Cmnt query failed, query: ",
                        new WebmasterErrorResponse.CmntErrorResponse(getClass(), exp), exp);
            }
            return true;
        });
    }

    @ExternalDependencyMethod("cmnt-lazy-init")
    public Map<String, String> lazyInitChats(LazyInitChatData lazyInitChatData) {

        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            Map<String, String> result = new HashMap<>();
            URI uri = UriComponentsBuilder.fromUriString(cmntUri + SIGN_API).build().toUri();
            HttpPut httpRequest = new HttpPut(uri);
            httpRequest.addHeader(TVMTokenService.TVM2_TICKET_HEADER, tvmTokenService.getToken());
            httpRequest.setEntity(new StringEntity(JsonMapping.writeValueAsString(lazyInitChatData), Charset.defaultCharset()));
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequest)) {
                if (httpResponse.getStatusLine().getStatusCode() / 100 != 2) {
                    log.error("Error in cmnt lazy init, status: {}, content: {}", httpResponse.getStatusLine().getStatusCode(), new String(httpResponse.getEntity().getContent().readAllBytes()));
                    throw new WebmasterException("Cmnt incorrect response", null);
                }
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                httpResponse.getEntity().writeTo(byteArrayOutputStream);
                final CmntAnswer cmntAnswer = OM.readValue(byteArrayOutputStream.toByteArray(), CmntAnswer.class);
                for (Sign sign : cmntAnswer.getSigns()) {
                    result.put(sign.getEntityId(), sign.getToken());
                }
            } catch (IOException exp) {
                log.error(exp.getMessage(), exp);
                throw new WebmasterException("Cmnt query failed, query: ",
                        new WebmasterErrorResponse.CmntErrorResponse(getClass(), exp), exp);
            }
            return result;
        });
    }

    @ExternalDependencyMethod("cmnt-chat-sign")
    public Map<String, String> chatsSignInit(long userId, final Collection<String> entitiesId) {

        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {

            Map<String, String> result = new HashMap<>();
            URI uri = UriComponentsBuilder.fromUriString(cmntUri + SIGN_API).build().toUri();
            HttpPut httpRequest = new HttpPut(uri);
            StringEntity params = new StringEntity(buildSignBody(userId, entitiesId), Charset.defaultCharset());
            httpRequest.setEntity(params);
            httpRequest.addHeader(TVMTokenService.TVM2_TICKET_HEADER, tvmTokenService.getToken());
            try (CloseableHttpResponse httpResponse = httpClient.execute(httpRequest)) {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                httpResponse.getEntity().writeTo(byteArrayOutputStream);
                final CmntAnswer cmntAnswer = OM.readValue(byteArrayOutputStream.toByteArray(), CmntAnswer.class);
                for (Sign sign : cmntAnswer.getSigns()) {
                    result.put(sign.getEntityId(), sign.getToken());
                }
            } catch (IOException exp) {

                throw new WebmasterException("Cmnt query failed, query: ",
                        new WebmasterErrorResponse.CmntErrorResponse(getClass(), exp), exp);
            }
            return result;

        });
    }

    private String buildInitBatchBody(Collection<String> entitiesId) {
        BatchQuery batchQuery = new BatchQuery();
        final List<Chat> collect = entitiesId.stream().map(e -> new Chat(e, SUPPLIER_SERVICE_SLUG_UGC)).collect(Collectors.toList());
        final ResponseFiled responseFiled = new ResponseFiled(true, false, false);
        batchQuery.setChats(collect);
        batchQuery.setResponseFields(responseFiled);
        try {
            return OM.writeValueAsString(batchQuery);
        } catch (JsonProcessingException e) {
            throw new WebmasterException("Json mapping error",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(CmntIntegraionService.class, "Json mapping error"), e);
        }
    }

    private String buildSignBody(long userId, Collection<String> entitiesId) {
        List<CmntEntity> entities = new ArrayList<>();
        for (String item : entitiesId) {
            CmntEntity official = new CmntEntity(item, List.of(new CmntItem("official", true)));
            entities.add(official);
        }
        try {
            return OM.writeValueAsString(new CmntSignQueryBody(userId, entities));
        } catch (JsonProcessingException e) {
            throw new WebmasterException("Json mapping error",
                    new WebmasterErrorResponse.InternalUnknownErrorResponse(CmntIntegraionService.class, "Json mapping error"), e);
        }
    }

    @Data
    private static class BatchQuery {
        List<Chat> chats;
        ResponseFiled responseFields;
    }

    @Data
    @AllArgsConstructor
    private static class Chat {
        String entityId;
        String supplierServiceSlug;
    }

    @Data
    @AllArgsConstructor
    private static class ResponseFiled {
        boolean chatId;
        boolean initeHash;
        boolean count;
    }

    @Data
    private static class CmntAnswer {
        List<Sign> signs;
    }

    @Data
    private static class Sign {
        String token;
        String type;
        String entityId;
    }

    @lombok.Value
    private static class CmntSignQueryBody {
        long uid;
        List<CmntEntity> entities;

    }

    @lombok.Value
    private static class CmntEntity {
        String entityId;
        List<CmntItem> items;
    }

    @lombok.Value
    private static class CmntItem {
        String type;
        @JsonProperty("isOfficial")
        boolean official;
    }

    @lombok.Value
    public static class CmntBriefInfo {
        @JsonProperty("feed")
        Map<String, CmntBriefFeed> feed;
    }

    @lombok.Value
    public static class CmntBriefFeed {
        @JsonProperty("count")
        int count;
    }

}
