package com.bytesw.bytebot.service.dashboard;

import com.bytesw.bytebot.bean.AverageBean;
import com.bytesw.bytebot.bean.RangeBean;
import com.bytesw.bytebot.bean.SummaryBean;
import com.bytesw.bytebot.bean.SessionByClientBean;
import com.bytesw.bytebot.jdbc.BotSessionJDCBRepository;
import com.bytesw.bytebot.jdbc.MessageJDBCRepository;
import com.bytesw.bytebot.jdbc.ResponseJDBCRepository;
import com.bytesw.xdf.exception.NotFoundException;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.bytesw.bytebot.model.enums.MetricMessageTypeEnum;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.time.*;
import java.util.*;

@Log4j2
@Service
public class OperativeDashboardService extends DashboardService {

    @Autowired
    private BotSessionJDCBRepository botSessionJDCBRepository;

    @Autowired
    private MessageJDBCRepository messageJDBCRepository;

    @Autowired
    private ResponseJDBCRepository responseJDBCRepository;

    @Override
    protected void completeData(OffsetDateTime startDate, OffsetDateTime endDate, Integer rangeMinutes, Map<String, Object> info) {

        if (startDate.isAfter(endDate)) {
            throw new NotFoundException("Invalid data range");
        }

        List<RangeBean> rangeList = calculateRangeList(startDate, endDate);
        List<RangeBean> rangeMinutsList = calculateMinutesRangeList(startDate, endDate, rangeMinutes);
        List<Object[]> messageByActivity = generateData(rangeList);

        //Cantidad de sesiones
        List<Object[]> sessions = generateDataVariablePeriod(rangeMinutsList, MetricMessageTypeEnum.SESSION.getName());
        int totalSessions = botSessionJDCBRepository.countSessionInRange(startDate, endDate);

        // Mensajes recibidos
        List<Object[]> recivedMessages = generateDataVariablePeriod(rangeMinutsList, MetricMessageTypeEnum.RECEIVED.getName());
        int totalrecivedMessages = messageJDBCRepository.countSessionInRange(startDate, endDate);
        // Mensajes enviados
        List<Object[]> responseMessages = generateDataVariablePeriod(rangeMinutsList, MetricMessageTypeEnum.SENT.getName());
        int totalresponseMessages = responseJDBCRepository.countSessionInRange(startDate, endDate);

        //Promedio primera respuesta en mili
        Object avgFRObject = botSessionJDCBRepository.getAvgFirstResponseTime(startDate, endDate);
        Double avgFirstResponse;
        if (avgFRObject instanceof Long) {
            avgFirstResponse = new Double((Long) avgFRObject);
        } else {
            avgFirstResponse = (Double) avgFRObject;
        }

        Double avgSessionsByCustomerRaw = botSessionJDCBRepository.getAvgSessionByCustomerInRange(startDate, endDate);
        // Tomar solo parte entera
        if (avgSessionsByCustomerRaw == null){
            avgSessionsByCustomerRaw = 0.0;
        }
        int avgSessionsByCustomer = avgSessionsByCustomerRaw.intValue();

        Timestamp timestamp = botSessionJDCBRepository.getLastDateInRage(startDate, endDate);

        if (timestamp == null) {
            throw new NotFoundException("No se encontró una sesión en el rango de fechas indicado");
        }

        //Calculando el tiempo de inactividad de la sesion
        OffsetDateTime lastSessionDate = OffsetDateTime.ofInstant(timestamp.toInstant(), ZoneId.systemDefault());
        Duration difference = Duration.between(lastSessionDate, endDate);

        // Adjuntando tiempo de inactividad
        SummaryBean sessionInactivity = new SummaryBean();
        BigInteger value = new BigInteger(String.valueOf(difference.getSeconds()));
        sessionInactivity.setValue(value);

        // Adjutando las sesiones totales
        Map<String,Object> totalSession = new HashMap<>();
        totalSession.put("value",totalSessions);
        totalSession.put("history",sessions);

        // Adjuntamos los mensajes recibidos
        Map<String, Object> recivedMessage = new HashMap<>();
        recivedMessage.put("value", totalrecivedMessages);
        recivedMessage.put("history", recivedMessages);

        // Adjuntamos los mensajes enviados
        Map<String, Object> responseMessage = new HashMap<>();
        responseMessage.put("value", totalresponseMessages);
        responseMessage.put("history", responseMessages);

        Map<String, Object> summary = new HashMap<>();
        summary.put("sessionInactivity", sessionInactivity);
        summary.put("totalSessions", totalSession);
        summary.put("totalSentMessages", responseMessage);
        summary.put("totalReceivedMessages", recivedMessage);

        AverageBean averageBean = new AverageBean();
        if (avgFirstResponse == null) {
            averageBean.setFirstResponseAverage(new BigDecimal(0));
        } else {
            averageBean.setFirstResponseAverage(new BigDecimal(avgFirstResponse));
        }
        averageBean.setSessionAverage(new BigDecimal(avgSessionsByCustomer));
        info.put("summary", summary);
        info.put("averages", averageBean);
        info.put("customerMessageDetail", messageByActivity);
    }

    private List<Object[]> generateDataVariablePeriod(List<RangeBean> rangeList, String metric) {
        List<Object[]> data = new ArrayList<>();
        Map<Long, Map<Integer, Integer>> accumulated = new HashMap<>();
        int cont = 0;
        for (RangeBean range : rangeList) {
            int cant = 0;
            if (metric.equals(MetricMessageTypeEnum.RECEIVED.getName())) {
                cant = messageJDBCRepository.countMessageInRangeHour(range.getStartDate(), range.getEndDate());
            } else if (metric.equals(MetricMessageTypeEnum.SENT.getName())) {
                cant = responseJDBCRepository.countSessionInRange(range.getStartDate(), range.getEndDate());
            }else if (metric.equals(MetricMessageTypeEnum.SESSION.getName())){
                cant = botSessionJDCBRepository.countSessionInRange(range.getStartDate(), range.getEndDate());
            }
            LocalDateTime times = range.getStartDate().toLocalDateTime();
            Long timestamp = Timestamp.valueOf(times).getTime();
            if (!accumulated.containsKey(timestamp)) {
                accumulated.put(timestamp, new HashMap<>());
            }
            accumulated.get(timestamp).put(cont, cant);
            cont++;
        }
        accumulated.keySet().stream().forEach(x -> {
            accumulated.get(x).keySet().stream().forEach(y -> {
                data.add(new Object[]{x, accumulated.get(x).get(y)});
            });
        });
        return data;
    }

    private List<Object[]> generateData(List<RangeBean> rangeList) {
        List<Object[]> data = new ArrayList<>();
        Map<Long, Map<Integer, Integer>> accumulated = new HashMap<>();
        for (RangeBean range : rangeList) {

            List<RangeBean> hourRanges = getRangeHours(range.getStartDate(), range.getEndDate());
            for (RangeBean hourRange : hourRanges) {
                int cant = messageJDBCRepository.countMessageInRangeHour(hourRange.getStartDate(), hourRange.getEndDate());
                int rangeHour = getRangeHour(hourRange.getStartDate().getHour());
                Long timestamp = getTimestamp(hourRange.getStartDate());
                if (!accumulated.containsKey(timestamp)) {
                    accumulated.put(timestamp, new HashMap<>());
                }
                if (!accumulated.get(timestamp).containsKey(rangeHour)) {
                    accumulated.get(timestamp).put(rangeHour, 0);
                }
                accumulated.get(timestamp).put(rangeHour, accumulated.get(timestamp).get(rangeHour) + cant);
            }
        }

        accumulated.keySet().stream().forEach(x -> {
            accumulated.get(x).keySet().stream().forEach(y -> {
                data.add(new Object[]{x, y, accumulated.get(x).get(y)});
            });
        });
        return data;
    }

    private int getRangeHour(int hour) {
        switch (hour) {
            case 3:
            case 4:
            case 5:
                return 1;

            case 6:
            case 7:
            case 8:
                return 2;

            case 9:
            case 10:
            case 11:
                return 3;

            case 12:
            case 13:
            case 14:
                return 4;

            case 15:
            case 16:
            case 17:
                return 5;

            case 18:
            case 19:
            case 20:
                return 6;

            case 21:
            case 22:
            case 23:
                return 7;

            default:
                return 0;
        }
    }

    private long getTimestamp(OffsetDateTime date) {
        OffsetDateTime dateTime;
        dateTime = date.withHour(0);
        dateTime = dateTime.withMinute(0);
        dateTime = dateTime.withSecond(0);
        dateTime = dateTime.withNano(0);
        return dateTime.toInstant().toEpochMilli();
    }

    private List<RangeBean> getRangeHours(OffsetDateTime startDate, OffsetDateTime endDate) {
        OffsetDateTime nextDate = getNextHour(startDate);
        OffsetDateTime startNextDate = startDate;
        List<RangeBean> rangeList = new ArrayList<>();
        while (nextDate.isBefore(endDate)) {
            RangeBean rangeBean = new RangeBean();
            rangeBean.setStartDate(startNextDate);
            rangeBean.setEndDate(nextDate);
            rangeList.add(rangeBean);
            startNextDate = nextDate;
            nextDate = getNextHour(nextDate);
        }
        if (startNextDate.isBefore(endDate)) {
            RangeBean rangeBean = new RangeBean();
            rangeBean.setStartDate(startNextDate);
            rangeBean.setEndDate(endDate);
            rangeList.add(rangeBean);
        }
        return rangeList;
    }
}
