package cz.senslog.connector.fetch.alapans;

import cz.senslog.connector.fetch.api.ConnectorFetcher;
import cz.senslog.connector.model.alapans.AlapansModel;
import cz.senslog.connector.model.alapans.UnitData;
import cz.senslog.connector.tools.http.HttpClient;
import cz.senslog.connector.tools.http.HttpRequest;
import cz.senslog.connector.tools.http.HttpResponse;
import cz.senslog.connector.tools.http.URLBuilder;
import cz.senslog.connector.tools.util.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;

import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
import static java.lang.String.format;

public class AlapansFetcher implements ConnectorFetcher<AlapansSession, AlapansModel> {

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

    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMddyyyy");

    private final AlapansConfig config;

    private final HttpClient httpClient;

    public AlapansFetcher(AlapansConfig config, HttpClient httpClient) {
        this.config = config;
        this.httpClient = httpClient;
    }

    public AlapansFetcher() {
        this(null, null);
    }

    @Override
    public void init() throws Exception {}

    @Override
    public AlapansModel fetch(Optional<AlapansSession> sessionOpt) {

        AlapansSession session = sessionOpt.orElse(AlapansSession.emptySession());

        LocalDate fromDate = sessionOpt.map(AlapansSession::getFromDate).orElse(config.getStartDate());
        LocalDate toDate = sessionOpt.map(AlapansSession::getToDate).orElse(fromDate.plusDays(config.getPeriod()));

        if (session.isEndOfIntervalIteration() &&
                (toDate.equals(LocalDate.now()) && !config.getNewDataExecutionTime().isBefore(LocalTime.now()))
        ) {
            return AlapansModel.empty();
        }

        ZoneId zoneId = config.getTimeZone().toZoneId();
        OffsetDateTime fromWithOffset = ZonedDateTime.of(LocalDateTime.of(fromDate, LocalTime.MIN), zoneId).toOffsetDateTime();
        OffsetDateTime toWithOffset = ZonedDateTime.of(LocalDateTime.of(toDate, LocalTime.MIN), zoneId).toOffsetDateTime();

        String intervalParam = String.format("%s/%s", fromDate.format(DATE_FORMATTER), toDate.format(DATE_FORMATTER));
        HttpRequest request = HttpRequest.newBuilder().GET()
                .url(URLBuilder
                        .newBuilder(config.getBaseUrl(), intervalParam)
                        .addParam("offset", session.getNextOffset())
                        .build())
                .build();

        HttpResponse response = httpClient.send(request);

        if (response.isError()) {
            logger.error(format("Can not get information about the sensors. %s", response.getBody()));
        } else {
            final Function<String, Double> PARSE_DOUBLE = s -> s != null ? Double.parseDouble(s) : null;

            ResponseData responseData = jsonToObject(response.getBody(), ResponseData.class);
            logger.info("Fetched {} records at the interval {} with the offset {}.", responseData.getCount(), intervalParam, session.getNextOffset());
            List<UnitData> unitDataList = new ArrayList<>(responseData.getCount());
            for (Map<String, String> item : responseData.getItems()) {
                unitDataList.add(new UnitData(
                        ZonedDateTime.of(LocalDateTime.parse(StringUtils.substringcut(item.get("s_date"), 1)), zoneId).toOffsetDateTime(),
                        item.get("device id"),
                        PARSE_DOUBLE.apply(item.get("air temperature")),
                        PARSE_DOUBLE.apply(item.get("air humidity")),
                        PARSE_DOUBLE.apply(item.get("soil temperature 1")),
                        PARSE_DOUBLE.apply(item.get("soil temperature 2")),
                        PARSE_DOUBLE.apply(item.get("soil moisture 1")),
                        PARSE_DOUBLE.apply(item.get("soil moisture 2")),
                        PARSE_DOUBLE.apply(item.get("battery voltage"))
                ));
            }

            if (responseData.isHasMore()) {
                session.setHasMore(true);
                session.setNextOffset(responseData.getOffset() + responseData.getLimit());
                session.setFromDate(fromDate);
                session.setToDate(toDate);
            } else {
                session.setHasMore(false);
                session.setNextOffset(0);
                session.setFromDate(toDate);
                session.setToDate(toDate.plusDays(config.getPeriod()));
            }

            return new AlapansModel(fromWithOffset, toWithOffset, unitDataList);
        }

        return new AlapansModel(fromWithOffset, toWithOffset, Collections.emptyList());
    }
}
