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

import static com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException.DOSSIER_NOT_FOUND_MESSAGE;

import java.time.OffsetDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import jakarta.transaction.Transactional;

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.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.settings.FileManagementServiceSettings;
import com.iqser.red.service.persistence.management.v1.processor.utils.DossierMapper;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierInformation;
import com.iqser.red.service.persistence.service.v1.api.shared.model.common.JSONPrimitive;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.CreateOrUpdateDossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierChange;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.search.v1.model.IndexMessageType;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class DossierManagementService {

    private final DossierService dossierService;
    private final FileStatusService fileStatusService;
    private final FileService fileService;
    private final IndexingService indexingService;
    private final DictionaryManagementService dictionaryManagementService;
    private final FileManagementServiceSettings fileManagementServiceSettings;


    public Set<DossierChange> changesSince(JSONPrimitive<OffsetDateTime> since) {

        return dossierService.changesSince(since.getValue());
    }


    @Transactional
    public Dossier addDossier(CreateOrUpdateDossierRequest dossierRequest) {

        var dossier = dossierService.addDossier(dossierRequest);

        return MagicConverter.convert(dossier, Dossier.class, new DossierMapper());
    }


    @Transactional
    public Dossier updateDossier(CreateOrUpdateDossierRequest dossierRequest, String dossierId) {

        return MagicConverter.convert(dossierService.updateDossier(dossierRequest, dossierId), Dossier.class, new DossierMapper());
    }


    @Transactional
    public void delete(String dossierId) {

        OffsetDateTime now = OffsetDateTime.now();

        DossierEntity dossier = dossierService.getDossierById(dossierId);
        if (dossier.getSoftDeletedTime() != null /*|| dossier.getHardDeletedTime() != null*/) {
            throw new DossierNotFoundException(String.format(DOSSIER_NOT_FOUND_MESSAGE, dossierId));
        }

        List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossierId);
        fileStatuses.stream().filter(fileStatus -> fileStatus.getDeleted() == null).forEach(fileStatus -> {
            fileService.softDeleteFile(dossierId, fileStatus.getId(), now);
            fileStatusService.setFileStatusDeleted(fileStatus.getId(), now);
        });

        dossierService.softDeleteDossier(dossierId, now);

    }


    @Transactional
    public List<Dossier> getAllDossiers(boolean includeArchived, boolean includeDeleted) {

        return getConvertedAllDossiers(dossierService.getAllDossiers(), includeArchived, includeDeleted);
    }


    private List<Dossier> getConvertedAllDossiers(List<DossierEntity> dossierEntityList, boolean includeArchived, boolean includeDeleted) {

        List<DossierEntity> dossierEntitiesList = dossierEntityList.stream().filter(dossierEntity -> dossierEntity.getHardDeletedTime() == null).collect(Collectors.toList());

        // return all dossiers
        if (includeDeleted && includeArchived) {
            return MagicConverter.convert(dossierEntitiesList, Dossier.class, new DossierMapper());
        }

        // return both active and archived&non-deleted dossiers
        if (includeArchived) {
            return MagicConverter.convert(dossierEntitiesList.stream().filter(p -> p.getSoftDeletedTime() == null).collect(Collectors.toList()),
                    Dossier.class,
                    new DossierMapper());
        }

        // return both active and deleted dossiers
        if (includeDeleted) {
            return MagicConverter.convert(dossierEntitiesList.stream().filter(p -> p.getArchivedTime() == null || p.getSoftDeletedTime() != null).collect(Collectors.toList()),
                    Dossier.class,
                    new DossierMapper());
        }

        // return only active dossiers
        return MagicConverter.convert(dossierEntitiesList.stream().filter(p -> p.getSoftDeletedTime() == null && p.getArchivedTime() == null).collect(Collectors.toList()),
                Dossier.class,
                new DossierMapper());
    }


    @Transactional
    public List<Dossier> getAllDossiersForDossierTemplateId(String dossierTemplateId, boolean includeArchived, boolean includeDeleted) {

        return getConvertedAllDossiers(dossierService.getAllDossiersForDossierTemplateId(dossierTemplateId), includeArchived, includeDeleted);

    }


    public DossierInformation getDossierInformation(List<String> filteredDossierIds) {

        DossierInformation dossierInformation = new DossierInformation();

        var dossiers = dossierService.getAllDossiers(filteredDossierIds != null ? filteredDossierIds : Collections.emptyList());

        dossiers.forEach(d -> {
            if (d.getHardDeletedTime() != null) {
                dossierInformation.setNumberOfHardDeletedDossiers(dossierInformation.getNumberOfHardDeletedDossiers() + 1);
            } else if (d.getSoftDeletedTime() != null) {
                dossierInformation.setNumberOfSoftDeletedDossiers(dossierInformation.getNumberOfSoftDeletedDossiers() + 1);
            } else if (d.getArchivedTime() != null) {
                dossierInformation.setNumberOfArchivedDossiers(dossierInformation.getNumberOfArchivedDossiers() + 1);
            } else {
                dossierInformation.setNumberOfActiveDossiers(dossierInformation.getNumberOfActiveDossiers() + 1);
            }
        });
        return dossierInformation;

    }


    @Transactional
    public Dossier getDossierById(String dossierId, boolean includeArchived, boolean includeDeleted) {

        DossierEntity dossier = dossierService.getDossierById(dossierId);
        if (dossier.getArchivedTime() != null && !includeArchived) {
            throw new DossierNotFoundException(String.format(DOSSIER_NOT_FOUND_MESSAGE, dossierId));
        }
        if (dossier.getSoftDeletedTime() != null && !includeDeleted) {
            throw new DossierNotFoundException(String.format(DOSSIER_NOT_FOUND_MESSAGE, dossierId));
        }
        return MagicConverter.convert(dossier, Dossier.class, new DossierMapper());
    }


    @Transactional
    public List<Dossier> getArchivedDossiers() {

        return getArchivedDossiersList(dossierService.getAllDossiers());
    }


    private List<Dossier> getArchivedDossiersList(List<DossierEntity> dossierEntityList) {

        var archivedDossiers = MagicConverter.convert(dossierEntityList.stream()
                .filter(p -> p.getArchivedTime() != null && p.getHardDeletedTime() == null && p.getSoftDeletedTime() == null)
                .collect(Collectors.toList()), Dossier.class, new DossierMapper());
        archivedDossiers.sort((dossier1, dossier2) -> dossier2.getArchivedTime().compareTo(dossier1.getArchivedTime()));

        return archivedDossiers;
    }


    @Transactional
    public List<Dossier> getArchivedDossiersForDossierTemplateId(String dossierTemplateId) {

        return getArchivedDossiersList(dossierService.getAllDossiersForDossierTemplateId(dossierTemplateId));
    }


    @Transactional
    public List<Dossier> getSoftDeletedDossiers() {

        var softDeletedDossiers = MagicConverter.convert(dossierService.getAllDossiers()
                .stream()
                .filter(p -> p.getSoftDeletedTime() != null && p.getHardDeletedTime() == null)
                .collect(Collectors.toList()), Dossier.class, new DossierMapper());
        softDeletedDossiers.sort((dossier1, dossier2) -> dossier2.getSoftDeletedTime().compareTo(dossier1.getSoftDeletedTime()));

        return softDeletedDossiers;
    }


    @Transactional
    public void hardDeleteDossiers(Set<String> dossierIds) {

        for (String dossierId : dossierIds) {
            DossierEntity dossier = dossierService.getDossierById(dossierId);
            dossierService.hardDeleteDossier(dossier.getId());
            List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossierId);
            fileStatuses.forEach(fileStatus -> {
                fileService.hardDeleteFile(dossierId, fileStatus.getId());
                fileStatusService.setFileStatusHardDeleted(fileStatus.getId());
            });
        }

    }


    public void undeleteDossiers(Set<String> dossierIds) {

        for (String dossierId : dossierIds) {
            var dossier = dossierService.getDossierById(dossierId);
            List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossierId);
            fileStatuses.forEach(fileStatus -> {
                if (fileStatus.getDeleted() != null && (fileStatus.getDeleted().equals(dossier.getSoftDeletedTime()) || fileStatus.getDeleted()
                        .isAfter(dossier.getSoftDeletedTime()))) {
                    fileService.undeleteFile(dossier.getDossierTemplateId(), dossierId, fileStatus.getId(), dossier.getSoftDeletedTime());
                    fileStatusService.setFileStatusUndeleted(fileStatus.getId());
                }
            });

            dossierService.undeleteDossier(dossierId);
        }

    }


    public void archiveDossiers(Set<String> dossierIds) {

        for (String dossierId : dossierIds) {
            DossierEntity dossier = dossierService.getDossierById(dossierId);
            if (dossier.getHardDeletedTime() == null) {
                dossierService.archiveDossier(dossier.getId());
                List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossier.getId());
                addToIndexingQueue(dossierId, fileStatuses);
            } else {
                log.error("Could not archive Dossier {}, because it is already deleted.", dossierId);
            }
        }

    }


    private void addToIndexingQueue(String dossierId, List<FileModel> fileStatuses) {

        fileStatuses.forEach(f -> indexingService.addToIndexingQueue(IndexMessageType.UPDATE, null, dossierId, f.getId(), 2));
    }


    public void unarchiveDossiers(Set<String> dossierIds) {

        for (String dossierId : dossierIds) {
            DossierEntity dossier = dossierService.getDossierById(dossierId);
            if (dossier.getHardDeletedTime() == null) {
                dossierService.unarchiveDossier(dossier.getId());
                List<FileModel> fileStatuses = fileStatusService.getDossierStatus(dossier.getId());
                addToIndexingQueue(dossierId, fileStatuses);
            } else {
                log.error("Could not unarchive Dossier {}, because it is deleted.", dossierId);
            }
        }

    }

}
