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

import org.springframework.data.domain.PageRequest;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
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.model.websocket.FileEventType;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.websocket.WebsocketService;
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 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 FileStatusService fileStatusService;
    private final FileManagementStorageService fileManagementStorageService;
    private final DossierPersistenceService dossierPersistenceService;
    private final DossierService dossierService;
    private final FileDeletionService fileDeletionService;
    private final WebsocketService websocketService;


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

        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, disableAutomaticAnalysis);
        } 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, disableAutomaticAnalysis);
        }

        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(DOSSIER_NOT_FOUND_MESSAGE);
        }

        OffsetDateTime softDeleteTime = OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS);
        fileDeletionService.softDeleteFiles(List.of(fileId), softDeleteTime);
        fileDeletionService.reindexDeletedFiles(dossierId, List.of(fileId));
        websocketService.sendFileEvent(dossierId, fileId, FileEventType.SOFT_DELETE);
    }


    @Async
    public void hardDeleteFiles(String dossierId, List<String> fileIds) {

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

        fileDeletionService.hardDeleteFiles(dossierId, fileIds);
        fileDeletionService.hardDeleteFileDataAndIndexUpdates(dossierId, fileIds);
        fileIds.forEach(fileId -> {
            websocketService.sendFileEvent(dossierId, fileId, FileEventType.HARD_DELETE);
        });
    }


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

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

        for (String fileId : fileIds) {
            FileModel fileStatus = fileStatusService.getStatus(fileId);
            OffsetDateTime softDeletedTime = fileStatus.getDeleted();
            ZonedDateTime zonedDeletedTime = softDeletedTime.atZoneSameInstant(ZoneId.systemDefault());
            OffsetDateTime adjustedSoftDeletedTime = zonedDeletedTime.toOffsetDateTime();
            fileDeletionService.undeleteFile(fileId, adjustedSoftDeletedTime);
        }
        fileDeletionService.reindexUndeletedFiles(dossier.getDossierTemplateId(), dossierId, fileIds);
        fileIds.forEach(fileId -> {
            websocketService.sendFileEvent(dossierId, fileId, FileEventType.UNDELETE);
        });
    }


    public void deleteViewerDocument(String dossierId, String fileId) {

        if (fileManagementStorageService.objectExists(dossierId, fileId, FileType.VIEWER_DOCUMENT)) {
            fileManagementStorageService.deleteObject(dossierId, fileId, FileType.VIEWER_DOCUMENT);
        }
    }

}
