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

import java.time.OffsetDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;

import com.google.common.collect.Sets;
import com.iqser.red.service.pdftron.redaction.v1.api.model.ByteContentDocument;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionOperation;
import com.iqser.red.service.pdftron.redaction.v1.api.model.highlights.TextHighlightConversionRequest;
import com.iqser.red.service.persistence.management.v1.processor.client.pdftronredactionservice.PDFTronClient;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.ConflictException;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.annotations.DeleteImportedRedactionsRequest;
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.ProcessingStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class ReanalysisService {

    private final FileStatusService fileStatusService;
    private final DossierPersistenceService dossierPersistenceService;
    private final IndexingService indexingService;
    private final PDFTronClient pDFTronRedactionClient;
    private final FileManagementStorageService fileManagementStorageService;


    public void reanalyzeDossier(String dossierId, boolean force) {

        var relevantFiles = getAllFilesForDossier(dossierId, true);
        reanalyseFiles(dossierId, force, relevantFiles);
    }


    private List<FileModel> getAllFilesForDossier(String dossierId, boolean filterOnlyValidFiles) {

        var dossier = dossierPersistenceService.getAndValidateDossier(dossierId);

        if (filterOnlyValidFiles) {
            return fileStatusService.getDossierStatus(dossier.getId())
                    .stream()
                    .filter(fileStatus -> !fileStatus.isSoftOrHardDeleted())
                    .filter(fileStatus -> !fileStatus.getWorkflowStatus().equals(WorkflowStatus.APPROVED))
                    .collect(Collectors.toList());
        } else {
            return fileStatusService.getDossierStatus(dossier.getId());
        }

    }


    private void reanalyseFiles(String dossierId, boolean force, List<FileModel> filesToReanalyse) {

        if (force) {
            filesToReanalyse.forEach(file -> {
                if (file.isFullAnalysisRequired()) {
                    fileStatusService.setStatusFullReprocess(dossierId, file.getId(), filesToReanalyse.size() == 1, false);
                } else {
                    fileStatusService.setStatusReprocess(dossierId, file.getId(), filesToReanalyse.size() == 1, true);
                }
            });
        } else {
            filesToReanalyse.stream().filter(FileModel::isReanalysisRequired).forEach(file -> {
                if (file.isFullAnalysisRequired()) {
                    fileStatusService.setStatusFullReprocess(dossierId, file.getId(), filesToReanalyse.size() == 1, false);
                } else {
                    fileStatusService.setStatusReprocess(dossierId, file.getId(), filesToReanalyse.size() == 1, true);
                }
            });
        }
    }


    public void reanalyzeFiles(String dossierId, Set<String> fileIds, boolean force) {

        var relevantFiles = getRelevantFiles(dossierId, fileIds);
        reanalyseFiles(dossierId, force, relevantFiles);
    }


    private List<FileModel> getRelevantFiles(String dossierId, Collection<String> fileIds) {

        var dossierFiles = getAllFilesForDossier(dossierId, false);

        var relevantFiles = new ArrayList<FileModel>();
        for (var fileId : fileIds) {
            var dossierFileOptional = dossierFiles.stream().filter(f -> f.getId().equals(fileId)).findAny();
            if (dossierFileOptional.isEmpty()) {
                throw new BadRequestException("Cannot reanalyse file from different dossier!");
            }

            var dossierFile = dossierFileOptional.get();
            if (dossierFile.isSoftOrHardDeleted()) {
                throw new BadRequestException("Cannot reanalyse deleted file!");
            }
            if (dossierFile.getWorkflowStatus() == WorkflowStatus.APPROVED) {
                throw new BadRequestException("Cannot reanalyse approved file!");
            }

            relevantFiles.add(dossierFile);

        }
        return relevantFiles;
    }


    public void ocrDossier(String dossierId) {

        var relevantFiles = getAllFilesForDossier(dossierId, true);

        relevantFiles.stream()
                .filter(fileStatus -> fileStatus.getOcrStartTime() == null)
                .filter(fileStatus -> fileStatus.getProcessingStatus().equals(ProcessingStatus.PROCESSED))
                .forEach(fileStatus -> fileStatusService.setStatusOcrQueued(dossierId, fileStatus.getId()));
    }


    public void ocrFile(String dossierId, String fileId, boolean force) {

        dossierPersistenceService.getAndValidateDossier(dossierId);
        FileModel dossierFile = fileStatusService.getStatus(fileId);

        if (!dossierFile.getProcessingStatus().equals(ProcessingStatus.PROCESSED) && !dossierFile.getProcessingStatus()
                .equals(ProcessingStatus.OCR_PROCESSING_QUEUED) && !dossierFile.getProcessingStatus().equals(ProcessingStatus.OCR_PROCESSING)) {
            throw new ConflictException("File is not processed");
        }

        if (force) {
            fileStatusService.setStatusOcrQueued(dossierId, fileId);
        } else {
            if (dossierFile.isSoftOrHardDeleted() || dossierFile.getWorkflowStatus().equals(WorkflowStatus.APPROVED)) {
                throw new ConflictException("Cannot analyse a deleted/approved file");
            }
            if (dossierFile.getOcrStartTime() != null) {
                throw new ConflictException("File already has been OCR processed");
            }

            ocrFiles(dossierId, Sets.newHashSet(fileId));
        }
    }


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

        var relevantFiles = getRelevantFiles(dossierId, fileIds);

        if (relevantFiles.stream()
                .anyMatch(fileStatus -> !fileStatus.getProcessingStatus().equals(ProcessingStatus.PROCESSED) && !fileStatus.getProcessingStatus()
                        .equals(ProcessingStatus.OCR_PROCESSING_QUEUED) && !fileStatus.getProcessingStatus().equals(ProcessingStatus.OCR_PROCESSING))) {
            throw new ConflictException("File is not processed");
        }

        relevantFiles.stream()
                .filter(fileStatus -> fileStatus.getOcrStartTime() == null)
                .forEach(fileStatus -> fileStatusService.setStatusOcrQueued(dossierId, fileStatus.getId()));
    }


    public void importRedactions(ByteContentDocument documentRequest) {

        try {
            pDFTronRedactionClient.importRedactions(documentRequest);
        } catch (Exception e) {
            throw new BadRequestException("File Not Valid PDF or number of pages do not match");
        }
        fileStatusService.setStatusFullReprocess(documentRequest.getDossierId(), documentRequest.getFileId(), true, false);
    }


    @SneakyThrows
    public void deleteImportedRedactions(DeleteImportedRedactionsRequest deleteImportedRedactionsRequest) {

        var importedRedactions = fileManagementStorageService.getImportedRedactions(deleteImportedRedactionsRequest.getDossierId(), deleteImportedRedactionsRequest.getFileId());
        importedRedactions.getImportedRedactions().forEach((key, value) -> value.removeIf(v -> deleteImportedRedactionsRequest.getAnnotationIds().contains(v.getId())));
        fileManagementStorageService.storeJSONObject(deleteImportedRedactionsRequest.getDossierId(),
                deleteImportedRedactionsRequest.getFileId(),
                FileType.IMPORTED_REDACTIONS,
                importedRedactions);
        fileStatusService.setStatusFullReprocess(deleteImportedRedactionsRequest.getDossierId(), deleteImportedRedactionsRequest.getFileId(), true, false);
    }


    public void reindex(String dossierId, boolean dropIndex, Set<String> fileIds) {

        indexingService.reindex(dossierId, fileIds, dropIndex);
    }


    public void convertTextHighlights(TextHighlightConversionRequest textHighlightRequest) {

        boolean hasTextHighlights = pDFTronRedactionClient.convertTextHighlights(textHighlightRequest);

        fileStatusService.updateHasHighlights(textHighlightRequest.getFileId(), hasTextHighlights);
        fileStatusService.updateFileModificationDate(textHighlightRequest.getFileId(), OffsetDateTime.now().truncatedTo(ChronoUnit.MILLIS));

        if (textHighlightRequest.getOperation().equals(TextHighlightConversionOperation.CONVERT)) {
            fileStatusService.setStatusFullReprocess(textHighlightRequest.getDossierId(), textHighlightRequest.getFileId(), true, false);
        }
    }

}
