package com.iqser.red.service.persistence.management.v1.processor.service;

import java.time.OffsetDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.DossierEntity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.LicenseReport;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.LicenseReportRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.license.MonthlyReportData;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class LicenseReportService {

    private static final ZoneId UTC_ZONE_ID = ZoneId.of("Z");
    private final FileStatusService fileStatusService;
    private final DossierService dossierService;


    public LicenseReport getLicenseReport(LicenseReportRequest licenseReportRequest) {

        if (licenseReportRequest.getEndDate().isBefore(licenseReportRequest.getStartDate())) {
            throw new BadRequestException("Invalid date period: End date is before start date.");
        }

        var files = fileStatusService.getStatusesAddedBefore(OffsetDateTime.ofInstant(licenseReportRequest.getEndDate(), UTC_ZONE_ID));
        var addDossiers = dossierService.getAllDossiers();

        files.sort(Comparator.comparing(FileModel::getAdded));

        var dossiersById = addDossiers.stream().collect(Collectors.toMap(DossierEntity::getId, Function.identity()));

        Map<YearMonth, Set<FileModel>> adds = new HashMap<>();
        Map<YearMonth, Set<FileModel>> softDeletes = new HashMap<>();
        Map<YearMonth, Set<FileModel>> archives = new HashMap<>();
        Map<YearMonth, Set<FileModel>> hardDeletes = new HashMap<>();
        Map<YearMonth, Set<FileModel>> ocrs = new HashMap<>();


        for (var file : files) {

            adds.computeIfAbsent(YearMonth.from(file.getAdded()), entry -> new HashSet<>()).add(file);

            if (file.getDeleted() != null && file.getDeleted().toInstant().isBefore(licenseReportRequest.getEndDate())) {
                softDeletes.computeIfAbsent(YearMonth.from(file.getDeleted()), entry -> new HashSet<>()).add(file);
            }
            if(dossiersById.get(file.getDossierId()).getSoftDeletedTime() != null && dossiersById.get(file.getDossierId()).getSoftDeletedTime().toInstant().isBefore(licenseReportRequest.getEndDate())){
                softDeletes.computeIfAbsent(YearMonth.from(dossiersById.get(file.getDossierId()).getSoftDeletedTime()), entry -> new HashSet<>()).add(file);
            }
            if (file.getHardDeletedTime() != null && file.getHardDeletedTime().toInstant().isBefore(licenseReportRequest.getEndDate())) {
                hardDeletes.computeIfAbsent(YearMonth.from(file.getHardDeletedTime()), entry -> new HashSet<>()).add(file);
            }
            if(dossiersById.get(file.getDossierId()).getHardDeletedTime() != null && dossiersById.get(file.getDossierId()).getHardDeletedTime().toInstant().isBefore(licenseReportRequest.getEndDate())){
                hardDeletes.computeIfAbsent(YearMonth.from(dossiersById.get(file.getDossierId()).getHardDeletedTime()), entry -> new HashSet<>()).add(file);
            }
            if (dossiersById.get(file.getDossierId()).getArchivedTime() != null && dossiersById.get(file.getDossierId())
                    .getArchivedTime()
                    .toInstant()
                    .isBefore(licenseReportRequest.getEndDate())) {
                archives.computeIfAbsent(YearMonth.from(dossiersById.get(file.getDossierId()).getArchivedTime()), entry -> new HashSet<>()).add(file);
            }
            if (file.getOcrStartTime() != null && file.getOcrStartTime().toInstant().isBefore(licenseReportRequest.getEndDate())) {
                ocrs.computeIfAbsent(YearMonth.from(file.getOcrStartTime()), entry -> new HashSet<>()).add(file);
            }
        }

        YearMonth currentMonth = !files.isEmpty() && files.get(0).getAdded().toInstant().isBefore(licenseReportRequest.getStartDate()) ? YearMonth.from(files.get(0)
                .getAdded()) : YearMonth.from(licenseReportRequest.getStartDate().atZone(UTC_ZONE_ID).toLocalDate());
        YearMonth endMonth = YearMonth.from(licenseReportRequest.getEndDate().atZone(UTC_ZONE_ID).toLocalDate());
        YearMonth reportStartMonth = YearMonth.from(licenseReportRequest.getStartDate().atZone(UTC_ZONE_ID).toLocalDate());

        List<MonthlyReportData> monthlyData = new ArrayList<>();

        // This values are what is in system at that point. Including all added values before the period.
        long activeFilesUploadedBytes = 0;
        long trashFilesUploadedBytes = 0;
        long archivedFilesUploadedBytes = 0;

        // This values are not what is currently in the system at that point. They are what is added in the period.
        int numberOfAnalyzedPages = 0;
        int numberOfOcrPages = 0;
        int numberOfAnalyzedFiles = 0;
        long analysedFilesBytes = 0;
        int numberOfOcrFiles = 0;

        while (!currentMonth.isAfter(endMonth)) {

            int currentMonthNumberOfAnalyzedPages = 0;
            int currentanalysedFilesBytes = 0;
            int currentMonthNumberOfOcrPages = 0;

            var addedFilesInMonth = adds.get(currentMonth);
            if (addedFilesInMonth != null) {
                for (var add : addedFilesInMonth) {
                    activeFilesUploadedBytes += add.getFileSize();

                    if (add.getAdded().toInstant().isAfter(licenseReportRequest.getStartDate())) {

                        numberOfAnalyzedPages += add.getNumberOfPages();
                        currentMonthNumberOfAnalyzedPages += add.getNumberOfPages();
                        numberOfAnalyzedFiles++;
                        analysedFilesBytes += add.getFileSize();
                        currentanalysedFilesBytes += add.getFileSize();
                    }
                }
            }

            var softDeletedFilesInMonth = softDeletes.get(currentMonth);
            if (softDeletedFilesInMonth != null) {
                for (var softDeleted : softDeletedFilesInMonth) {
                    if (dossiersById.get(softDeleted.getDossierId()).getArchivedTime() != null) {
                        archivedFilesUploadedBytes -= softDeleted.getFileSize();
                        trashFilesUploadedBytes += softDeleted.getFileSize();
                    } else {
                        activeFilesUploadedBytes -= softDeleted.getFileSize();
                        trashFilesUploadedBytes += softDeleted.getFileSize();
                    }
                }
            }

            var archivedFilesInMonth = archives.get(currentMonth);
            if (archivedFilesInMonth != null) {
                for (var archived : archivedFilesInMonth) {
                    activeFilesUploadedBytes -= archived.getFileSize();
                    archivedFilesUploadedBytes += archived.getFileSize();
                }
            }

            var hardDeletedFilesInMonth = hardDeletes.get(currentMonth);
            if (hardDeletedFilesInMonth != null) {
                for (var hardDeleted : hardDeletedFilesInMonth) {
                    if (hardDeleted.getDeleted() != null || dossiersById.get(hardDeleted.getDossierId()).getSoftDeletedTime() != null) {
                        trashFilesUploadedBytes -= hardDeleted.getFileSize();
                    } else {
                        activeFilesUploadedBytes -= hardDeleted.getFileSize();
                    }
                }
            }

            var ocrFilesInMonth = ocrs.get(currentMonth);
            if (ocrFilesInMonth != null) {
                for (var ocrFile : ocrFilesInMonth) {

                    if (ocrFile.getOcrStartTime().toInstant().isAfter(licenseReportRequest.getStartDate())) {
                        numberOfOcrPages += ocrFile.getNumberOfPages(); // We count the entire document if ocr is performed.
                        currentMonthNumberOfOcrPages += ocrFile.getNumberOfPages();
                        numberOfOcrFiles++;
                    }
                }
            }

            if (currentMonth.equals(reportStartMonth) || currentMonth.isAfter(reportStartMonth)) {

                var monthEndDate = currentMonth.atEndOfMonth().atTime(23, 59).atZone(UTC_ZONE_ID).toInstant();

                monthlyData.add(MonthlyReportData.builder()
                        .startDate(currentMonth.atDay(1).atStartOfDay(UTC_ZONE_ID).toInstant())
                        .endDate(monthEndDate.isBefore(licenseReportRequest.getEndDate()) ? monthEndDate : licenseReportRequest.getEndDate())
                        .activeFilesUploadedBytes(activeFilesUploadedBytes)
                        .trashFilesUploadedBytes(trashFilesUploadedBytes)
                        .archivedFilesUploadedBytes(archivedFilesUploadedBytes)
                        .totalFilesUploadedBytes(activeFilesUploadedBytes + trashFilesUploadedBytes + archivedFilesUploadedBytes)
                        .numberOfAnalyzedPages(currentMonthNumberOfAnalyzedPages)
                        .numberOfOcrPages(currentMonthNumberOfOcrPages)
                        .analysedFilesBytes(currentanalysedFilesBytes)
                        .build());
            }

            currentMonth = currentMonth.plusMonths(1);
        }

        return LicenseReport.builder()
                .totalFilesUploadedBytes(activeFilesUploadedBytes + trashFilesUploadedBytes + archivedFilesUploadedBytes)
                .activeFilesUploadedBytes(activeFilesUploadedBytes)
                .trashFilesUploadedBytes(trashFilesUploadedBytes)
                .archivedFilesUploadedBytes(archivedFilesUploadedBytes)
                .numberOfAnalyzedPages(numberOfAnalyzedPages)
                .numberOfOcrPages(numberOfOcrPages)
                .numberOfAnalyzedFiles(numberOfAnalyzedFiles)
                .analysedFilesBytes(analysedFilesBytes)
                .numberOfOcrFiles(numberOfOcrFiles)
                .numberOfDossiers(addDossiers.stream().filter(dossier -> //
                        dossier.getDate().toInstant().isAfter(licenseReportRequest.getStartDate()) //
                                && dossier.getDate().toInstant().isBefore(licenseReportRequest.getEndDate()) //
                                && (dossier.getHardDeletedTime() == null || dossier.getHardDeletedTime()
                                .isAfter(OffsetDateTime.ofInstant(licenseReportRequest.getEndDate(), UTC_ZONE_ID)))).collect(Collectors.toSet()).size())
                .startDate(licenseReportRequest.getStartDate())
                .endDate(licenseReportRequest.getEndDate())
                .monthlyData(monthlyData)
                .build();
    }

}
