package cz.senslog.connector.fetch.azure;

import com.google.gson.reflect.TypeToken;
import cz.senslog.connector.tools.http.*;
import cz.senslog.connector.tools.util.Tuple;
import cz.senslog.connector.fetch.api.ConnectorFetcher;
import cz.senslog.connector.fetch.api.FetchProxySession;
import cz.senslog.connector.model.azure.AzureModel;
import cz.senslog.connector.model.config.HostConfig;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.time.OffsetDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
import static cz.senslog.connector.fetch.azure.AzureSession.emptySession;
import static cz.senslog.connector.model.converter.AzureUnitConverter.convertUnitIdToEui;
import static java.time.format.DateTimeFormatter.ofPattern;
import static java.util.Collections.emptyList;

class AzureProxySession extends FetchProxySession<AzureSession, AzureModel> {

    private static final Logger logger = LogManager.getLogger(AzureProxySession.class);

    protected static class ObservationInfo { OffsetDateTime timeStamp; Long unitId; }

    private final HttpClient httpClient;
    private final AzureSessionProxyConfig config;
    private Tuple<Boolean, HttpCookie> authCookie;


    public AzureProxySession(
            ConnectorFetcher<AzureSession, AzureModel> instance,
            AzureSessionProxyConfig config,
            HttpClient httpClient
    ) {
        super(instance);
        this.config = config;
        this.httpClient = httpClient;
        this.authCookie = Tuple.of(false, null);
    }

    private synchronized HttpCookie getAuthCookie() {
        if (authCookie.getItem1()) {
            return authCookie.getItem2();
        }

        final String baseUrl = config.getLastObservationHost().getDomain();
        HttpRequest request = HttpRequest.newBuilder().GET()
                .url(URLBuilder.newBuilder(baseUrl, "/ControllerServlet")
                        .addParam("username", "watchdog")
                        .addParam("password", "HAFhaf")
                        .build()
                ).build();
        logger.info("Getting new auth cookie from the server: {}.", baseUrl);

        HttpResponse response = httpClient.send(request);
        logger.info("Received new auth token with the status '{}' from the server {}.", response.getStatus(), baseUrl);

        if (response.isError()) {
            logger.warn("Authorization failed. Error code {} with the reason '{}'.", response.getStatus(), response.getBody());
            HttpCookie cookie = HttpCookie.empty();
            authCookie = Tuple.of(false, cookie);
            return cookie;
        }

        final Type lastObsType = new TypeToken<Map<String, Object>>() {}.getType();
        Map<String, Object> jsonResponse = jsonToObject(response.getBody(), lastObsType);

        if (!jsonResponse.containsKey("sessionid")) {
            logger.error("Authorization failed. JSON does not contain session id. {}", response.getBody());
            HttpCookie cookie = HttpCookie.empty();
            authCookie = Tuple.of(false, cookie);
            return cookie;
        }

        try {
            URI baseUri = new URI(baseUrl);
            String sessionId = (String) jsonResponse.get("sessionid");
            HttpCookie cookie = new HttpCookie("JSESSIONID", sessionId, baseUri.getHost(), baseUri.getHost());
            authCookie = Tuple.of(true, cookie);
            return cookie;
        } catch (URISyntaxException e) {
            logger.catching(e);
            HttpCookie cookie = HttpCookie.empty();
            authCookie = Tuple.of(false, cookie);
            return cookie;
        }
    }

    @Override
    protected AzureSession preProcessing(Optional<AzureSession> previousSession) {

//        HttpCookie authCookie = getAuthCookie();
//        if (!authCookie.isSecure()) {
//            logger.warn("Auth cookie is not valid to be used.");
//            return emptySession();
//        }

        HostConfig host = config.getLastObservationHost();
        logger.info("Getting last observations from {}.", host.getDomain());

        HttpRequest request = HttpRequest.newBuilder().GET()
            //    .addCookie(authCookie)
                .url(URLBuilder.newBuilder(host.getDomain(), host.getPath())
                        .addParam("Operation", "GetLastObservations")
                        .addParam("group", config.getGroup())
                        .addParam("user", config.getUser())
                        .build())
                .build();
        logger.info("Creating a http request to {}.", request);

        HttpResponse response = httpClient.send(request);
        logger.info("Received a response with a status: {} for the domain {}.", response.getStatus(), host.getDomain());

        if (response.isError()) {
            logger.error("Can not get data from the server {}. Error {} {}",
                    host.getDomain(), response.getStatus(), response.getBody());
            return emptySession();
        }

        logger.debug("Parsing body of the response to the list of class {}.", ObservationInfo.class);
        Type lastObsType = new TypeToken<Collection<ObservationInfo>>() {}.getType();
        List<ObservationInfo> lastObservations = jsonToObject(response.getBody(), lastObsType,
                Tuple.of(OffsetDateTime.class, el -> OffsetDateTime.parse(el, ofPattern("yyyy-MM-dd HH:mm:ssX")))
        );

        AzureSession session = previousSession.filter(AzureSession::isActive)
                .orElse(new AzureSession(true));
        logger.debug("Created a new session of {}.", AzureSession.class);

        logger.debug("Filling the new session of last timestamps from observations per each station.");
        for (ObservationInfo info : lastObservations) {
            String azureId = convertUnitIdToEui(info.unitId);
            if (azureId != null) {
                session.addLastUnitTime(azureId, info.timeStamp);
            }
        }

        logger.info("Loaded and successfully parsed {} last observations from stations.", lastObservations.size());
        return session;
    }

    @Override
    protected void postProcessing(Optional<AzureModel> returnedModel, Optional<AzureSession> currentSession) {
        if (currentSession.isPresent() && currentSession.get().isActive()) {
            AzureSession session = currentSession.get();
            // TODO persistence the current session
        }
    }
}