package cz.senslog.connector.fetch.openmeteo;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import cz.senslog.connector.fetch.api.ConnectorFetcher;
import cz.senslog.connector.model.api.VoidSession;
import cz.senslog.connector.model.openmeteo.OpenMeteoModel;
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 org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.*;
import java.util.*;

import static cz.senslog.connector.tools.json.BasicJson.jsonToObject;
import static java.time.format.DateTimeFormatter.ofPattern;

public class OpenMeteoFetcher implements ConnectorFetcher<VoidSession, OpenMeteoModel> {

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

    private final HttpClient httpClient;
    private final OpenMeteoConfig config;

    private final Set<Station> stations;
    private LocalDate currentDate;


    public OpenMeteoFetcher(OpenMeteoConfig config, HttpClient httpClient) {
        this.httpClient = httpClient;
        this.config = config;
        this.stations = new HashSet<>();
    }

    @Override
    public void init() {
        this.currentDate = config.getStartDate();
    }

    @Override
    public OpenMeteoModel fetch(Optional<VoidSession> session) {

        stations.clear();

        if (currentDate.isAfter(LocalDate.now())) {
            LocalDateTime futureExecution = LocalDateTime.of(currentDate, LocalTime.now());
            logger.info("Waiting for current date. Execution in {} minutes", Duration.between(futureExecution, LocalDateTime.now()).toMinutes());
            return new OpenMeteoModel(null, null, Collections.emptyList());
        }

        {
            HttpRequest.Builder req = HttpRequest.newBuilder().GET()
                    .url(URLBuilder.newBuilder(config.getAllowedStationURL()).build());

            HttpResponse res = httpClient.send(req.build());

            if (res.isError()) {
                throw logger.throwing(new RuntimeException(String.format("Error while getting the metno stations from %s.", config.getAllowedStationURL())));
            }

            JsonArray listJSON = jsonToObject(res.getBody(), JsonArray.class);
            for (JsonElement element : listJSON) {
                JsonObject stationJSON = element.getAsJsonObject();
                JsonObject geometryJSON = stationJSON.get("geometry").getAsJsonObject();
                String geometryType = geometryJSON.get("type").getAsString();
                if (geometryType.equals("Point")) {
                    JsonArray coordinatesJSON = geometryJSON.get("coordinates").getAsJsonArray();
                    stations.add(new Station(coordinatesJSON.get(0).getAsDouble(), coordinatesJSON.get(1).getAsDouble()));
                }
            }
        }

        List<JsonObject> results = new ArrayList<>(stations.size());
        for (Station station : stations) {
            HttpRequest.Builder req = HttpRequest.newBuilder().GET()
                    .url(URLBuilder.newBuilder(config.getUrl())
                            .addParam("latitude", Double.toString(station.getLatitude()))
                            .addParam("longitude", Double.toString(station.getLongitude()))
                            .addParam("start_date", currentDate.format(ofPattern("yyyy-MM-dd")))
                            .addParam("end_date", currentDate.format(ofPattern("yyyy-MM-dd")))
//                            .addParam("timezone", station.timezone)
//                            .addParam("past_days", Integer.toString(station.past_days))
                            .addParam("hourly", String.join(",", config.getHourly()))
                            .addParam("models", config.getModels())
                            .build());

            HttpResponse res = httpClient.send(req.build());

            if (res.isError()) {
                logger.warn("Open Meteo station lat: {}, lon: {} failed to fetch. Error: {}", station.getLatitude(), station.getLongitude(), res.getBody());
                continue;
            }

            JsonObject geoJson = jsonToObject(res.getBody(), JsonObject.class);
            results.add(geoJson);
        }

        OffsetDateTime currentDateTime = ZonedDateTime.of(currentDate, LocalTime.now(), config.getTimeZone().toZoneId()).toOffsetDateTime();

        currentDate = currentDate.plusDays(1);

        return new OpenMeteoModel(currentDateTime, currentDateTime, results);
    }
}