package com.iqser.red.persistence.service.v1.external.api.impl.controller;

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.*;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.google.common.collect.Sets;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.ReanalysisService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.ReanalysisResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PageExclusionRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.WorkflowStatus;

import feign.FeignException;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
public class ReanalysisController implements ReanalysisResource {

    private static final String DOSSIER_ID = "dossierId";
    private final ReanalysisService reanalysisService;
    private final FileStatusManagementService fileStatusManagementService;
    private final AuditPersistenceService auditPersistenceService;
    private final AccessControlService accessControlService;

    @PreAuthorize("hasAuthority('" + REANALYZE_DOSSIER + "')")
    public void reanalyzeDossier(@PathVariable(DOSSIER_ID) String dossierId, @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {

        try {
            accessControlService.verifyUserHasViewPermissions(dossierId);
        } catch (AccessDeniedException e) {
            throw new NotFoundException("Object not found");
        }

        accessControlService.verifyUserHasAccessPermissions(dossierId);
        reanalysisService.reanalyzeDossier(dossierId, force);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierId)
                .category(AuditCategory.DOSSIER.name())
                .message("Reanalyse dossier was triggered")
                .build());

    }

    @PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
    public void reanalyzeFile(@PathVariable(DOSSIER_ID) String dossierId,
                              @PathVariable(FILE_ID) String fileId,
                              @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {

        reanalysisService.reanalyzeFiles(dossierId, Sets.newHashSet(fileId), force);
        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierId)
                .category(AuditCategory.DOCUMENT.name())
                .message("Reanalyse file was triggered")
                .details(Map.of(DOSSIER_ID, dossierId))
                .build());
    }


    @Override
    @PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
    public void reanalyzeFilesForDossier(@PathVariable(DOSSIER_ID) String dossierId,
                                         @RequestBody List<String> fileIds,
                                         @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {

        reanalysisService.reanalyzeFiles(dossierId, new HashSet<>(fileIds), force);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierId)
                .category(AuditCategory.DOCUMENT.name())
                .message("Reanalyse files was triggered")
                .details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
                .build());

    }


    @Override
    @PreAuthorize("hasAuthority('" + REANALYZE_DOSSIER + "')")
    public void ocrDossier(@PathVariable(DOSSIER_ID) String dossierId) {

        try {
            accessControlService.verifyUserHasViewPermissions(dossierId);
        } catch (AccessDeniedException e) {
            throw new NotFoundException("Object not found");
        }

        accessControlService.verifyUserHasAccessPermissions(dossierId);
        reanalysisService.ocrDossier(dossierId);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierId)
                .category(AuditCategory.DOSSIER.name())
                .message("OCR and reanalyse dossier was triggered")
                .build());

    }


    @Override
    @PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
    public void ocrFile(@PathVariable(DOSSIER_ID) String dossierId,
                        @PathVariable(FILE_ID) String fileId,
                        @RequestParam(value = FORCE_PARAM, required = false, defaultValue = FALSE) boolean force) {

        validateOCR(dossierId, fileId);
        reanalysisService.ocrFile(dossierId, fileId, force);
        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierId)
                .category(AuditCategory.DOCUMENT.name())
                .message("OCR and reanalyse file was triggered")
                .details(Map.of(DOSSIER_ID, dossierId))
                .build());

    }


    @Override
    @PreAuthorize("hasAuthority('" + REANALYZE_FILE + "')")
    public void ocrFiles(@PathVariable(DOSSIER_ID) String dossierId, @RequestBody Set<String> fileIds) {

        fileIds.forEach(fileId -> validateOCR(dossierId, fileId));
        reanalysisService.ocrFiles(dossierId, fileIds);
        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierId)
                .category(AuditCategory.DOCUMENT.name())
                .message("OCR and reanalyse was triggered")
                .details(Map.of(DOSSIER_ID, dossierId, "number", fileIds.size()))
                .build());

    }

    @PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
    public void toggleAutomaticAnalysis(@PathVariable(DOSSIER_ID) String dossierId,
                                        @PathVariable(FILE_ID) String fileId,
                                        @RequestParam(EXCLUDED_STATUS_PARAM) boolean excludedFromAutomaticAnalysis) {

        accessControlService.verifyUserIsReviewer(dossierId, fileId);
        fileStatusManagementService.toggleAutomaticAnalysis(dossierId, fileId, excludedFromAutomaticAnalysis);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(fileId)
                .category(AuditCategory.DOCUMENT.name())
                .message("Toggle Exclusion status: File excluded from automatic analysis: " + excludedFromAutomaticAnalysis)
                .build());

    }


    @Override
    @PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
    public void toggleExclusion(@PathVariable(DOSSIER_ID) String dossierId,
                                @PathVariable(FILE_ID) String fileId,
                                @RequestParam(name = EXCLUDED_STATUS_PARAM, required = false, defaultValue = "false") boolean excluded) {

        var status = fileStatusManagementService.getFileStatus(fileId);
        if (!(status.getAssignee() == null && status.isExcluded())) { // Needed to include documents after 3.0 migration.
            accessControlService.verifyUserIsReviewer(dossierId, fileId);
        }
        fileStatusManagementService.toggleExclusion(dossierId, fileId, excluded);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(fileId)
                .category(AuditCategory.DOCUMENT.name())
                .message("Toggle Exclusion status: File excluded from analysis: " + excluded)
                .build());

    }

    @PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
    public void toggleAutomaticAnalysisForList(@PathVariable(DOSSIER_ID) String dossierId,
                                               @RequestBody Set<String> fileIds,
                                               @RequestParam(EXCLUDED_STATUS_PARAM) boolean excludedFromAutomaticAnalysis) {

        List<String> errorIds = new ArrayList<>();
        for (var fileId : fileIds) {
            try {
                this.toggleAutomaticAnalysis(dossierId, fileId, excludedFromAutomaticAnalysis);
            } catch (FeignException e) {
                errorIds.add(fileId);
            }
        }
        if (!errorIds.isEmpty()) {
            throw new BadRequestException("Failed to delete files with ids: " + errorIds);
        }
    }

    @PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_FILE + "')")
    public void toggleExclusionForList(@PathVariable(DOSSIER_ID) String dossierId,
                                       @RequestBody Set<String> fileIds,
                                       @RequestParam(name = EXCLUDED_STATUS_PARAM, required = false, defaultValue = "false") boolean excluded) {

        List<String> errorIds = new ArrayList<>();
        for (var fileId : fileIds) {
            try {
                this.toggleExclusion(dossierId, fileId, excluded);
            } catch (FeignException e) {
                errorIds.add(fileId);
            }
        }
        if (!errorIds.isEmpty()) {
            throw new BadRequestException("Failed to delete files with ids: " + errorIds);
        }
    }


    @Override
    @PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_PAGES + "')")
    public void excludePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody PageExclusionRequest pageExclusionRequest) {

        accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);

        Set<Integer> excludedPages = new HashSet<>();
        for (var pageRange : pageExclusionRequest.getPageRanges()) {
            for (int i = pageRange.getStartPage(); i <= pageRange.getEndPage(); i++) {
                excludedPages.add(i);
            }
        }
        fileStatusManagementService.excludePages(dossierId, fileId, excludedPages);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(fileId)
                .category(AuditCategory.DOCUMENT.name())
                .message("Page exclusions added for file")
                .build());

    }


    @Override
    @PreAuthorize("hasAuthority('" + EXCLUDE_INCLUDE_PAGES + "')")
    public void includePages(@PathVariable(DOSSIER_ID) String dossierId, @PathVariable(FILE_ID) String fileId, @RequestBody PageExclusionRequest pageInclusionRequest) {

        accessControlService.verifyUserIsReviewerOrApprover(dossierId, fileId);

        Set<Integer> includePages = new HashSet<>();
        for (var pageRange : pageInclusionRequest.getPageRanges()) {
            for (int i = pageRange.getStartPage(); i <= pageRange.getEndPage(); i++) {
                includePages.add(i);
            }
        }
        fileStatusManagementService.includePages(dossierId, fileId, includePages);

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(fileId)
                .category(AuditCategory.DOCUMENT.name())
                .message("Page inclusions added for file")
                .build());
    }


    @PreAuthorize("hasAuthority('" + REINDEX + "')")
    public void reindex(@RequestParam(value = "dossierId", required = false) String dossierId,
                        @RequestParam(value = "dropIndex", required = false, defaultValue = FALSE) boolean dropIndex,
                        @RequestBody List<String> fileIds) {

        reanalysisService.reindex(dossierId, dropIndex, new HashSet<>(fileIds));

        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId("redaction")
                .category(AuditCategory.INDEX.name())
                .message("Reindexing has been triggered" + (dropIndex ? " (with drop index)." : "."))
                .build());

    }


    private void validateOCR(String dossierId, String fileId) {

        var status = fileStatusManagementService.getFileStatus(fileId);
        if (status.getWorkflowStatus() == WorkflowStatus.APPROVED) {
            throw new BadRequestException("Cannot OCR approved file");
        }
        if (status.getAssignee() == null) {
            accessControlService.verifyUserIsMemberOrApprover(dossierId);
        } else {
            accessControlService.verifyUserIsReviewer(dossierId, fileId);
        }
    }

}
