package ru.yandex.webmaster3.core.turbo.adv;

import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
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.HttpPatch;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
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.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.turbo.adv.model.IAdvRequest;
import ru.yandex.webmaster3.core.turbo.adv.model.IAdvUpdateRequest;
import ru.yandex.webmaster3.core.turbo.adv.model.response.ExtendedAttributeInfoResponse;
import ru.yandex.webmaster3.core.turbo.adv.model.response.ResponseCreate;
import ru.yandex.webmaster3.core.turbo.adv.model.response.ResponseFind;
import ru.yandex.webmaster3.core.util.JavaMethodWitness;
import ru.yandex.webmaster3.core.util.json.JsonMapping;

/**
 * ishalaru
 * 30.07.2020
 **/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Autowired})
public class AdvertisingIntegrationService extends AbstractExternalAPIService {
    private static final int REQUEST_TIMEOUT = 10_000;
    private static final int SOCKET_TIMEOUT = 10_000;
    private static final String FILTER_QUERY = "filter";
    private static final String FIELDS = "fields";
    private static final String AUTHORIZATION = "Authorization";
    private static final String APPLICATION_FORMAT = "application/vnd.api+json";
    private static final String VERSION = "/v1";
    @Value("${webmaster3.core.pi.uri}")
    private String url;
    @Value("#{'token '+ '${secrets.pi.api.key}'}")
    private String authKey;

    private CloseableHttpClient httpClient;

    public void init() {

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

        httpClient = HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionTimeToLive(10, TimeUnit.MINUTES)
                .build();
    }

    @SneakyThrows
    @ExternalDependencyMethod("create-objects")
    public ResponseCreate create(IAdvRequest request) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            URIBuilder uriBuilder = new URIBuilder(url + VERSION + "/" + request.getComponentType());
            HttpPost query = new HttpPost(uriBuilder.build());
            query.addHeader(AUTHORIZATION, authKey);
            query.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_FORMAT);
            query.addHeader(HttpHeaders.ACCEPT, APPLICATION_FORMAT);
            String entityStr = JsonMapping.writeValueAsString(new Data(request.getRequestObject()));
            query.setEntity(new StringEntity(entityStr, ContentType.APPLICATION_JSON));
            log.info("create: {}, json: {}", query.getURI().toString(), entityStr);

            try (final CloseableHttpResponse response = httpClient.execute(query)) {
                byte[] responseBytes = response.getEntity().getContent().readAllBytes();
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode / 100 != 2) {
                    log.error("Adv create objects error. Status: {}, error: {}, request: {}",
                            statusCode, new String(responseBytes), request.getRequestObject());
                    throw buildException("Invalid advertise create response code: " + statusCode, responseBytes);
                }

                log.info("create: pi response: {}", new String(responseBytes));
                return JsonMapping.readValue(responseBytes, ResponseCreate.class);
            }
        });
    }

    @SneakyThrows
    @ExternalDependencyMethod("find-exists-objects")
    public ResponseFind find(IAdvRequest request) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            URIBuilder uriBuilder = new URIBuilder(url + VERSION + "/" + request.getComponentType());
            uriBuilder.addParameter(FILTER_QUERY, request.getCheckExistQuery());
            HttpGet query = new HttpGet(uriBuilder.build().toString());
            query.addHeader(AUTHORIZATION, authKey);
            query.addHeader(HttpHeaders.ACCEPT, APPLICATION_FORMAT);
            query.addHeader(HttpHeaders.CONTENT_ENCODING, StandardCharsets.UTF_8.name());

            log.info("find: {}", query.getURI().toString());
            try (final CloseableHttpResponse response = httpClient.execute(query)) {
                byte[] responseBytes = response.getEntity().getContent().readAllBytes();
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode / 100 != 2) {
                    log.error("Adv find objects error. Status: {}, error: {}", statusCode, new String(responseBytes));
                    throw buildException("Invalid advertise find response code: " + statusCode, null);
                }

                log.info("find: pi response: {}", responseBytes);
                return JsonMapping.readValue(responseBytes, ResponseFind.class);
            }
        });
    }


    public Optional<ExtendedAttributeInfoResponse> getBlockInfo(String code) {
        try {
            ExtendedAttributeInfoResponse blocks = this.
                    find(code, "blocks", "show_video,design_templates,site_version,page_id,type,dsp_blocks", ExtendedAttributeInfoResponse.class);
            if (blocks.getErrors() != null && blocks.getErrors().size() > 0) {
                return Optional.empty();
            }
            return Optional.of(blocks);
        } catch (Exception exp) {
            return Optional.empty();
        }
    }

    @SneakyThrows
    public ResponseCreate find(String id, String type, String fields) {
        return find(id, type, fields, ResponseCreate.class);
    }

    @SneakyThrows
    @ExternalDependencyMethod("get-state")
    public <V> V find(String id, String type, String fields, Class<V> clazz) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            URIBuilder uriBuilder = new URIBuilder(url + VERSION + "/" + type + "/" + id);
            uriBuilder.addParameter(FIELDS + "[" + type + "]", fields);
            HttpGet query = new HttpGet(uriBuilder.build().toString());
            query.addHeader(AUTHORIZATION, authKey);
            query.addHeader(HttpHeaders.ACCEPT, APPLICATION_FORMAT);

            log.info("find2: {}", query.getURI().toString());
            try (final CloseableHttpResponse response = httpClient.execute(query)) {
                byte[] responseBytes = response.getEntity().getContent().readAllBytes();
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode / 100 != 2) {
                    log.error("Adv find2 pi error. Status: {}, pi response: {}", statusCode, new String(responseBytes));
                    throw buildException("Invalid advertise find2 response code: " + statusCode, null);
                }

                log.info("find2: pi response: {}", new String(responseBytes));
                return JsonMapping.readValue(responseBytes, clazz);
            }
        });
    }

    @SneakyThrows
    @ExternalDependencyMethod("update-exists-objects")
    public ResponseCreate update(IAdvUpdateRequest request) {
        return trackQuery(new JavaMethodWitness() {
        }, ALL_ERRORS_INTERNAL, () -> {
            URIBuilder uriBuilder = new URIBuilder(url + VERSION + "/" + request.getUriSuffix());
            HttpPatch query = new HttpPatch(uriBuilder.build());
            query.addHeader(AUTHORIZATION, authKey);
            query.addHeader(HttpHeaders.CONTENT_TYPE, APPLICATION_FORMAT);
            query.addHeader(HttpHeaders.ACCEPT, APPLICATION_FORMAT);
            String entityStr = JsonMapping.writeValueAsString(new Data(request.getRequestUpdateObject()));
            query.setEntity(new StringEntity(entityStr, ContentType.APPLICATION_JSON));

            log.info("update: {}, json: {}", query.getURI().toString(), entityStr);
            try (final CloseableHttpResponse response = httpClient.execute(query)) {
                byte[] responseBytes = response.getEntity().getContent().readAllBytes();
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode / 100 != 2) {
                    log.error("Adv update exists objects error. Status: {}, pi response: {}", statusCode, new String(responseBytes));
                    throw buildException("Invalid advertise update response code: " + statusCode, null);
                }

                log.info("update: pi response: {}", new String(responseBytes));
                return JsonMapping.readValue(responseBytes, ResponseCreate.class);
            }
        });
    }

    private WebmasterException buildException(String message, byte[] responseBytes) {
        if (responseBytes != null) {
            try {
                final ResponseCreate responseCreate = JsonMapping.readValue(responseBytes, ResponseCreate.class);
                if (responseCreate.getErrors() != null && !responseCreate.getErrors().isEmpty()) {
                    log.error("Error on PI side: {}", responseCreate.getErrors());
                    return new AdvIntegrationException(
                            JsonMapping.writeValueAsString(responseCreate.getErrors()),
                            new WebmasterErrorResponse.InternalUnknownErrorResponse(AdvertisingIntegrationService.class, message));
                }
            } catch (Exception exp) {
                log.error(exp.getMessage(), exp);
            }
        }

        return new WebmasterException(message, new WebmasterErrorResponse.InternalUnknownErrorResponse(
                AdvertisingIntegrationService.class, message), null);
    }

    @lombok.Value
    public static class Data {
        @JsonProperty("data")
        Object data;
    }

}
