package cz.senslog.connector.fetch.fofr;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import cz.senslog.connector.fetch.api.ConnectorFetcher;
import cz.senslog.connector.model.api.VoidSession;
import cz.senslog.connector.model.fofr.FofrModel;
import cz.senslog.connector.model.fofr.ParcelUpdate;
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.Encoding;
import cz.senslog.connector.tools.util.Tuple;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

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

public class FofrFetcher implements ConnectorFetcher<VoidSession, FofrModel> {

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


    private final FofrConfig config;
    private final HttpClient httpClient;
    private final XmlMapper xmlMapper;

    private Map<String, WarehouseInfo> warehouses;
    private Map<Integer, String> orderStates;

    protected FofrFetcher(FofrConfig config, HttpClient httpClient) {
        this.config = config;
        this.httpClient = httpClient;
        this.xmlMapper = new XmlMapper();

        this.warehouses = Collections.emptyMap();
        this.orderStates = Collections.emptyMap();
    }

    @Override
    public void init() throws Exception {
        {
            HttpRequest request = HttpRequest.newBuilder().GET()
                    .url(URLBuilder.newBuilder(config.getWarehouseLocationsUrl()).build())
                    .build();

            HttpResponse response = httpClient.send(request);

            if (response.isError()) {
                throw logger.throwing(new Exception(format(
                        "Can not get information about the warehouses. %s", response.getBody()
                )));
            }

            String responseBody = response.getBody();
            List<?> responseDTO = jsonToObject(responseBody, List.class);
            warehouses = responseDTO.stream().map(Map.class::cast).map(w -> new WarehouseInfo(
                        (String)w.get("KOD_STREDISKA"),
                        Double.parseDouble((String)w.get("GEOCODE_LAT")),
                        Double.parseDouble((String)w.get("GEOCODE_LNG"))
                    )).collect(Collectors.toMap(WarehouseInfo::getCode, Function.identity()));

            for (WarehouseInfo missingWarehouse : config.getMissingWarehouses()) {
                warehouses.put(missingWarehouse.getCode(), missingWarehouse);
            }
        }

        {

            HttpRequest request = HttpRequest.newBuilder().GET()
                    .url(URLBuilder.newBuilder(config.getOrderStatesUrl()).build())
                    .build();

            HttpResponse response = httpClient.send(request, Encoding.WINDOWS_1250);

            if (response.isError()) {
                throw logger.throwing(new Exception(format(
                        "Can not get information about the statuses. %s", response.getBody()
                )));
            }

            String responseBody = response.getBody();
            List<?> xml = xmlMapper.readValue(responseBody, List.class);
            orderStates = xml.stream().map(Map.class::cast).map(m -> Tuple.of(
                    Integer.parseInt((String)m.get("ID")),
                    (String)m.get("POPIS"))
            ).collect(Collectors.toMap(Tuple::getItem1, Tuple::getItem2));

        }
    }

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

        List<OrderInfo> orderInfos = Collections.emptyList();
//
//        {
//            HttpRequest request = HttpRequest.newBuilder().GET()
//                    .url(URLBuilder.newBuilder(config.getActiveOrdersUrl()).build())
//                    .build();
//
//            HttpResponse response = httpClient.send(request);
//
//            if (response.isOk()) {
//                String responseBody = response.getBody();
//                List<?> orderList = jsonToObject(responseBody, List.class);
//                orderInfos = new ArrayList<>(orderList.size());
//                for (Object orderO : orderList) {
//                    if (orderO instanceof Map) {
//                        Map<?, ?> orderMap = (Map<?, ?>) orderO;
//                        orderInfos.add(new OrderInfo(
//                                ((Double) orderMap.get("orderId")).longValue(),
//                                ((Double) orderMap.get("parcelId")).longValue(),
//                                ((Double) orderMap.get("unitId")).longValue()
//                        ));
//                    }
//                }
//            }
//        }

        orderInfos = Arrays.asList(new OrderInfo(0, 2590161966L, 0));

        List<ParcelUpdate> parcelsToUpdate = new ArrayList<>();
        for (OrderInfo orderInfo : orderInfos) {

            long parcelId = orderInfo.getParcelId();

            HttpRequest reqTrackingInfo = HttpRequest.newBuilder().GET()
                    .url(URLBuilder.newBuilder(config.getParcelTrackingUrl())
                            .addParam("shipment", parcelId)
                            .build())
                    .build();

            HttpResponse resTrackingInfo = httpClient.send(reqTrackingInfo, Encoding.WINDOWS_1250);
            if (resTrackingInfo.isError()) {
                logger.error(resTrackingInfo.getBody()); continue;
            }

            try {
                String trackingInfoBody = resTrackingInfo.getBody();
                ParcelTrackingStatus trackingInfo = xmlMapper.readValue(trackingInfoBody, ParcelTrackingStatus.class);

                if (trackingInfo.getId() != (parcelId)) {
                    logger.warn("Received tracking ID does not match: {} != {}", parcelId, trackingInfo.getId()); continue;
                }

                // TODO compare last status with the one from session if any updates
                // TODO if no update, continue to next parcelId

                if (trackingInfo.getStatuses().isEmpty()) {
                    logger.warn("No status for the parcel ID {}", trackingInfo.getId()); continue;
                }

                int parcelYear = trackingInfo.getStatuses().get(0).getTimestamp().getYear();

                HttpRequest reqParcelInfo = HttpRequest.newBuilder().GET()
                        .url(URLBuilder.newBuilder(String.format(config.getParcelInfoUrl(), parcelId, parcelYear)
                                ).build())
                        .build();

                HttpResponse resParcelInfo = httpClient.send(reqParcelInfo);
                if (resParcelInfo.isError()) {
                    logger.error(resParcelInfo.getBody()); continue;
                }

                String parcelInfoBody = resParcelInfo.getBody();
                List<?> parcelInfoList = jsonToObject(parcelInfoBody, List.class);
                if (parcelInfoList.isEmpty()) {
                    logger.error("Parcel info of the ID " + parcelId + " not found"); continue;
                }

                if (!(parcelInfoList.get(0) instanceof Map)) {
                    logger.error("Wrong type of parcel info."); continue;
                }

                Map<?,?> parcelInfo = (Map<?,?>) parcelInfoList.get(0);
                long shipmentNum = Long.parseLong((String) parcelInfo.get("shipment_num"));
                if (parcelId != (shipmentNum)) {
                    logger.warn("Received parcel info data does not match: {} != {}", parcelId, trackingInfo.getId()); continue;
                }

                String officeBranchCode = (String) parcelInfo.get("kod_pobocky");
                WarehouseInfo officeBranch = warehouses.get(officeBranchCode);

                int lastTrackingStatusCode = ((Double)parcelInfo.get("posledni_statut")).intValue();
                List<ParcelTrackingStatus.Status> statuses = trackingInfo.getStatuses();
                if (statuses.get(statuses.size()-1).getCode() != lastTrackingStatusCode) {
                    statuses.sort(Comparator.comparing(ParcelTrackingStatus.Status::getTimestamp));
                }
                List<ParcelInfo.TrackingStatus> trackingStatuses = new ArrayList<>(statuses.size());
                for (ParcelTrackingStatus.Status status : statuses) {
                    String statusName = orderStates.get(status.getCode());
                    if (statusName == null) {
                        logger.warn("No status name for '{}'", status.getCode());
                    }
                    WarehouseInfo transitWarehouse = warehouses.get(status.getWarehouseCode());
                    if (transitWarehouse == null) {
                        logger.warn("No warehouse for '{}'", status.getWarehouseCode());
                    }
                    trackingStatuses.add(new ParcelInfo.TrackingStatus(
                            status.getCode(), statusName, status.getTimestamp(), transitWarehouse
                    ));
                }

                LocalDateTime exportDate = LocalDateTime.parse((String)parcelInfo.get("datum_exportu"), ofPattern("yyyy-MM-dd HH:mm:ss"));

                ParcelInfo parcel = new ParcelInfo(parcelId, officeBranch, exportDate, trackingStatuses,
                        new ParcelInfo.Address(
                                (String) parcelInfo.get("odes_firma"),
                                (String) parcelInfo.get("odes_stat"),
                                (String) parcelInfo.get("odes_mesto"),
                                (String) parcelInfo.get("odes_ulice"),
                                (String) parcelInfo.get("odes_psc")
                        ),
                        new ParcelInfo.Address(
                                (String) parcelInfo.get("prij_firma"),
                                (String) parcelInfo.get("prij_stat"),
                                (String) parcelInfo.get("prij_mesto"),
                                (String) parcelInfo.get("prij_ulice"),
                                (String) parcelInfo.get("prij_psc")
                        )
                );

                if (parcel.lastTrackingStatus().getId() == config.getDeliveryStatus()) {
                    // TODO parcel is delivered
                    // GET GPS coordination from delivery address
                }

                System.out.println(objectToJson(parcel));

            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
        }

        return new FofrModel(parcelsToUpdate, OffsetDateTime.now(), OffsetDateTime.now());
    }


}
