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.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Set;

import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.exception.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.ViewedPagesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.AddRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.CommentPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ForceRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.LegalBasisChangePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RecategorizationPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.RemoveRedactionPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.annotations.ResizeRedactionPersistenceService;
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.file.AddFileRequest;
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.dossiertemplate.dossier.file.FileType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;
import com.iqser.red.service.search.v1.model.IndexMessageType;

import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * Provides the internal interface between upload request and the actual persistence.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileService {

    private final AddRedactionPersistenceService addRedactionPersistenceService;
    private final CommentPersistenceService commentPersistenceService;
    private final ForceRedactionPersistenceService forceRedactionPersistenceService;
    private final RemoveRedactionPersistenceService removeRedactionPersistenceService;
    private final FileStatusService fileStatusService;
    private final ViewedPagesPersistenceService viewedPagesPersistenceService;
    private final FileManagementStorageService fileManagementStorageService;
    private final DossierPersistenceService dossierPersistenceService;
    private final RecategorizationPersistenceService recategorizationPersistenceService;
    private final LegalBasisChangePersistenceService legalBasisChangePersistenceService;
    private final ResizeRedactionPersistenceService resizeRedactionPersistenceService;
    private final IndexingService indexingService;
    private final DossierService dossierService;


    public JSONPrimitive<String> upload(AddFileRequest request, boolean keepManualRedactions, long size) {

        dossierPersistenceService.getAndValidateDossier(request.getDossierId());

        var existingStatus = retrieveStatus(request.getFileId());

        if (existingStatus != null && !existingStatus.isSoftOrHardDeleted()) {
            if (existingStatus.getWorkflowStatus().equals(WorkflowStatus.APPROVED)) {
                throw new ConflictException("File already exists in status APPROVED");
            }

            if (existingStatus.getAssignee() != null && !request.getUploader().equalsIgnoreCase(existingStatus.getAssignee())) {
                throw new ConflictException("Only the current assignee can overwrite a file");
            }
        }

        if (existingStatus != null) {
            // the file is already uploaded, just reanalyse it.

            fileStatusService.overwriteFile(request.getDossierId(), request.getFileId(), request.getUploader(), request.getFilename(), keepManualRedactions);
        } else {
            // the file is new, should create a new status for it.
            log.info("File {} has no status yet, creating one and setting to unprocessed.", request.getFilename());
            fileStatusService.createStatus(request.getDossierId(), request.getFileId(), request.getUploader(), request.getFilename(), size);
        }

        return new JSONPrimitive<>(request.getFileId());
    }


    private FileModel retrieveStatus(String fileId) {

        try {
            return fileStatusService.getStatus(fileId);
        } catch (NotFoundException e) {
            // it's a new File.
            return null;
        }
    }


    @Transactional
    public void deleteFile(String dossierId, String fileId) {

        var dossier = dossierService.getDossierById(dossierId);
        if (dossier.getSoftDeletedTime() != null) {
            throw new DossierNotFoundException(String.format(DOSSIER_NOT_FOUND_MESSAGE, dossierId));
        }

        OffsetDateTime softDeleteTime = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
        softDeleteFile(dossierId, fileId, softDeleteTime);
        fileStatusService.setFileStatusDeleted(fileId, softDeleteTime);
    }


    public void softDeleteFile(String dossierId, String fileId, OffsetDateTime softDeletedTime) {

        forceRedactionPersistenceService.findForceRedactions(fileId, false).forEach(annotation -> {
            forceRedactionPersistenceService.softDelete(fileId, annotation.getId().getAnnotationId(), softDeletedTime);
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), false)
                    .forEach(comment -> commentPersistenceService.softDelete(comment.getId(), softDeletedTime));
        });

        removeRedactionPersistenceService.findRemoveRedactions(fileId, false).forEach(annotation -> {
            removeRedactionPersistenceService.softDelete(fileId, annotation.getId().getAnnotationId(), softDeletedTime);
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), false)
                    .forEach(comment -> commentPersistenceService.softDelete(comment.getId(), softDeletedTime));
        });

        addRedactionPersistenceService.findAddRedactions(fileId, false).forEach(annotation -> {
            addRedactionPersistenceService.softDelete(fileId, annotation.getId().getAnnotationId(), softDeletedTime);
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), false)
                    .forEach(comment -> commentPersistenceService.softDelete(comment.getId(), softDeletedTime));
        });

        recategorizationPersistenceService.findRecategorizations(fileId, false).forEach(recatigorization -> {
            recategorizationPersistenceService.softDelete(fileId, recatigorization.getId().getAnnotationId(), softDeletedTime);
            commentPersistenceService.findCommentsByAnnotationId(fileId, recatigorization.getId().getAnnotationId(), false)
                    .forEach(comment -> commentPersistenceService.softDelete(comment.getId(), softDeletedTime));
        });

        resizeRedactionPersistenceService.findResizeRedactions(fileId, false).forEach(annotation -> {
            resizeRedactionPersistenceService.softDelete(fileId, annotation.getId().getAnnotationId(), softDeletedTime);
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), false)
                    .forEach(comment -> commentPersistenceService.softDelete(comment.getId(), softDeletedTime));
        });

        legalBasisChangePersistenceService.findLegalBasisChanges(fileId, false).forEach(legalBasisChange -> {
            legalBasisChangePersistenceService.softDelete(fileId, legalBasisChange.getId().getAnnotationId(), softDeletedTime);
            commentPersistenceService.findCommentsByAnnotationId(fileId, legalBasisChange.getId().getAnnotationId(), false)
                    .forEach(comment -> commentPersistenceService.softDelete(comment.getId(), softDeletedTime));
        });

        viewedPagesPersistenceService.deleteForFile(fileId);
        indexingService.addToIndexingQueue(IndexMessageType.UPDATE, null, dossierId, fileId, 2);
    }


    public void hardDeleteFiles(String dossierId, Set<String> fileIds) {

        var dossier = dossierService.getDossierById(dossierId);
        if (dossier.getSoftDeletedTime() != null) {
            throw new DossierNotFoundException(String.format(DOSSIER_NOT_FOUND_MESSAGE, dossierId));
        }

        for (String fileId : fileIds) {
            hardDeleteFile(dossierId, fileId);
            fileStatusService.setFileStatusHardDeleted(fileId);
        }

    }


    public void hardDeleteFile(String dossierId, String fileId) {

        Arrays.stream(FileType.values()).forEach(fileType -> {
            try {
                fileManagementStorageService.deleteObject(dossierId, fileId, fileType);
            } catch (Exception e) {
                log.warn("Failed to physically delete file: {} with type {}", fileId, fileType);
            }
        });

        forceRedactionPersistenceService.findForceRedactions(fileId, true).forEach(annotation -> {
            forceRedactionPersistenceService.hardDelete(fileId, annotation.getId().getAnnotationId());
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true)
                    .forEach(comment -> commentPersistenceService.hardDelete(comment.getId()));
        });

        removeRedactionPersistenceService.findRemoveRedactions(fileId, true).forEach(annotation -> {
            removeRedactionPersistenceService.hardDelete(fileId, annotation.getId().getAnnotationId());
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true)
                    .forEach(comment -> commentPersistenceService.hardDelete(comment.getId()));
        });

        addRedactionPersistenceService.findAddRedactions(fileId, true).forEach(annotation -> {
            addRedactionPersistenceService.hardDelete(fileId, annotation.getId().getAnnotationId());
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true)
                    .forEach(comment -> commentPersistenceService.hardDelete(comment.getId()));
        });

        recategorizationPersistenceService.findRecategorizations(fileId, true).forEach(recatigorization -> {
            recategorizationPersistenceService.hardDelete(fileId, recatigorization.getId().getAnnotationId());
            commentPersistenceService.findCommentsByAnnotationId(fileId, recatigorization.getId().getAnnotationId(), true)
                    .forEach(comment -> commentPersistenceService.hardDelete(comment.getId()));
        });

        resizeRedactionPersistenceService.findResizeRedactions(fileId, true).forEach(annotation -> {
            resizeRedactionPersistenceService.hardDelete(fileId, annotation.getId().getAnnotationId());
            commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true)
                    .forEach(comment -> commentPersistenceService.hardDelete(comment.getId()));
        });
        indexingService.addToDeleteFromIndexQueue(dossierId, fileId, 2);

    }


    public void undeleteFiles(String dossierId, Set<String> fileIds) {

        var dossier = dossierService.getDossierById(dossierId);
        if (dossier.getSoftDeletedTime() != null) {
            throw new DossierNotFoundException(String.format(DOSSIER_NOT_FOUND_MESSAGE, dossierId));
        }

        for (String fileId : fileIds) {

            FileModel fileStatus = fileStatusService.getStatus(fileId);
            OffsetDateTime softDeletedTime = fileStatus.getDeleted();

            undeleteFile(dossier.getDossierTemplateId(), dossierId, fileId, softDeletedTime);
            fileStatusService.setFileStatusUndeleted(fileId);
        }
    }


    public void undeleteFile(String dossierTemplateId, String dossierId, String fileId, OffsetDateTime softDeletedTime) {

        forceRedactionPersistenceService.findForceRedactions(fileId, true).forEach(annotation -> {
            if (annotation.getSoftDeletedTime().equals(softDeletedTime) || annotation.getSoftDeletedTime().isAfter(softDeletedTime)) {
                forceRedactionPersistenceService.undelete(fileId, annotation.getId().getAnnotationId());
                commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true).forEach(comment -> {
                    if (comment.getSoftDeletedTime().equals(softDeletedTime) || comment.getSoftDeletedTime().isAfter(softDeletedTime)) {
                        commentPersistenceService.undelete(comment.getId());
                    }
                });
            }
        });

        removeRedactionPersistenceService.findRemoveRedactions(fileId, true).forEach(annotation -> {
            if (annotation.getSoftDeletedTime().equals(softDeletedTime) || annotation.getSoftDeletedTime().isAfter(softDeletedTime)) {
                removeRedactionPersistenceService.undelete(fileId, annotation.getId().getAnnotationId());
                commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true).forEach(comment -> {
                    if (comment.getSoftDeletedTime().equals(softDeletedTime) || comment.getSoftDeletedTime().isAfter(softDeletedTime)) {
                        commentPersistenceService.undelete(comment.getId());
                    }
                });
            }
        });

        addRedactionPersistenceService.findAddRedactions(fileId, true).forEach(annotation -> {
            if (annotation != null && annotation.getSoftDeletedTime() != null && (annotation.getSoftDeletedTime().equals(softDeletedTime) || annotation.getSoftDeletedTime()
                    .isAfter(softDeletedTime))) {
                addRedactionPersistenceService.undelete(fileId, annotation.getId().getAnnotationId());
                commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true).forEach(comment -> {
                    if (comment.getSoftDeletedTime().equals(softDeletedTime) || comment.getSoftDeletedTime().isAfter(softDeletedTime)) {
                        commentPersistenceService.undelete(comment.getId());
                    }
                });
            }
        });

        recategorizationPersistenceService.findRecategorizations(fileId, true).forEach(recatigorization -> {
            if (recatigorization.getSoftDeletedTime().equals(softDeletedTime) || recatigorization.getSoftDeletedTime().isAfter(softDeletedTime)) {
                recategorizationPersistenceService.undelete(fileId, recatigorization.getId().getAnnotationId());
                commentPersistenceService.findCommentsByAnnotationId(fileId, recatigorization.getId().getAnnotationId(), true).forEach(comment -> {
                    if (comment.getSoftDeletedTime().equals(softDeletedTime) || comment.getSoftDeletedTime().isAfter(softDeletedTime)) {
                        commentPersistenceService.undelete(comment.getId());
                    }
                });
            }
        });

        resizeRedactionPersistenceService.findResizeRedactions(fileId, true).forEach(annotation -> {
            if (annotation.getSoftDeletedTime().equals(softDeletedTime) || annotation.getSoftDeletedTime().isAfter(softDeletedTime)) {
                resizeRedactionPersistenceService.undelete(fileId, annotation.getId().getAnnotationId());
                commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true).forEach(comment -> {
                    if (comment.getSoftDeletedTime().equals(softDeletedTime) || comment.getSoftDeletedTime().isAfter(softDeletedTime)) {
                        commentPersistenceService.undelete(comment.getId());
                    }
                });
            }
        });

        legalBasisChangePersistenceService.findLegalBasisChanges(fileId, true).forEach(annotation -> {
            if (annotation.getSoftDeletedTime().equals(softDeletedTime) || annotation.getSoftDeletedTime().isAfter(softDeletedTime)) {
                legalBasisChangePersistenceService.undelete(fileId, annotation.getId().getAnnotationId());
                commentPersistenceService.findCommentsByAnnotationId(fileId, annotation.getId().getAnnotationId(), true).forEach(comment -> {
                    if (comment.getSoftDeletedTime().equals(softDeletedTime) || comment.getSoftDeletedTime().isAfter(softDeletedTime)) {
                        commentPersistenceService.undelete(comment.getId());
                    }
                });
            }
        });

        indexingService.addToIndexingQueue(IndexMessageType.UPDATE, dossierTemplateId, dossierId, fileId, 2);

    }

}
