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

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DOSSIER_ATTRIBUTES;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;

import java.util.HashSet;
import java.util.Set;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
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.iqser.red.persistence.service.v1.external.api.impl.controller.DossierController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DossierTemplateController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.DownloadController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.StatusController;
import com.iqser.red.service.persistence.management.v1.processor.entity.dossier.FileEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierAttributesManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DownloadStatusPersistenceService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.FileStatus;
import com.iqser.red.service.persistence.service.v1.api.shared.model.PrepareDownloadWithOptionRequest;
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.DownloadFileType;
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.DossierAttribute;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.DossierAttributes;
import com.iqser.red.service.persistence.service.v2.api.external.model.DocuMineDossierRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DossierList;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.DownloadStatus;
import com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;

@RestController
@RequiredArgsConstructor
@Tag(name = "2. Dossier endpoints", description = "Provides operations related to dossiers")
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
public class DossierControllerV2 implements DossierResource {

    DossierTemplateController dossierTemplateController;
    DossierController dossierController;
    AccessControlService accessControlService;
    DossierAttributesManagementService dossierAttributesManagementService;
    AuditPersistenceService auditPersistenceService;
    DownloadController downloadController;
    StatusController statusController;
    DownloadStatusPersistenceService downloadStatusPersistenceService;
    CurrentApplicationTypeProvider currentApplicationTypeProvider;


    public DossierList getDossiers(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                   @RequestParam(name = INCLUDE_ACTIVE_PARAM, defaultValue = "true", required = false) boolean includeActive,
                                   @RequestParam(name = INCLUDE_ARCHIVED_PARAM, defaultValue = "false", required = false) boolean includeArchived,
                                   @RequestParam(name = INCLUDE_SOFT_DELETED_PARAM, defaultValue = "false", required = false) boolean includeSoftDeleted) {

        dossierTemplateController.getDossierTemplate(dossierTemplateId);

        var dossiers = dossierController.getDossiersForDossierTemplate(dossierTemplateId, includeArchived, includeSoftDeleted);

        if (!includeActive) {
            return new DossierList(dossiers.stream()
                                           .filter(dossier -> dossier.getSoftDeletedTime() != null || dossier.getArchivedTime() != null)
                                           .toList());
        }

        return new DossierList(dossiers);
    }


    public Dossier getDossier(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                              @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                              @RequestParam(name = INCLUDE_SOFT_DELETED_PARAM, defaultValue = "false", required = false) boolean includeSoftDeleted) {

        dossierTemplateController.getDossierTemplate(dossierTemplateId);

        return dossierController.getDossier(dossierId, true, includeSoftDeleted);
    }


    public ResponseEntity<Dossier> createDossierOrUpdateDossier(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                                                @RequestBody DocuMineDossierRequest dossier) {

        dossierTemplateController.getDossierTemplate(dossierTemplateId);

        return dossierController.createDossierOrUpdateDossier(mapToDossierRequest(dossierTemplateId, dossier));
    }


    public void deleteDossier(@Parameter(name = DOSSIER_TEMPLATE_ID_PARAM, description = "The identifier of the dossier template that is used for the dossier.", required = true) @PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                              @Parameter(name = DOSSIER_ID_PARAM, description = "The identifier of the dossier to retrieve.", required = true) @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                              @RequestParam(name = DELETE_PERMANENTLY_PARAM, defaultValue = "false", required = false) boolean deletePermanently) {

        dossierTemplateController.getDossierTemplate(dossierTemplateId);

        if (deletePermanently) {
            dossierController.hardDeleteDossiers(Sets.newHashSet(dossierId));
        } else {
            dossierController.deleteDossier(dossierId);
        }
    }


    @PreAuthorize("hasAuthority('" + WRITE_DOSSIER_ATTRIBUTES + "')")
    public void setDossierAttributes(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                     @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                     @RequestBody DossierAttributes dossierAttributes) {

        dossierTemplateController.getDossierTemplate(dossierTemplateId);

        accessControlService.checkDossierExistenceAndAccessPermissionsToDossier(dossierId);
        accessControlService.verifyUserIsDossierOwner(dossierId);

        var dossierAttributeList = dossierAttributes.getAttributeIdToValue().entrySet()
                .stream()
                .map(entry -> new DossierAttribute(dossierId, entry.getKey(), entry.getValue()))
                .toList();

        dossierAttributesManagementService.setDossierAttributes(dossierId, dossierAttributeList);
        auditPersistenceService.insertRecord(AuditRequest.builder()
                                                     .userId(KeycloakSecurity.getUserId())
                                                     .objectId(dossierId)
                                                     .category(AuditCategory.DOSSIER.name())
                                                     .message("Changed dossier attributes.")
                                                     .build());
    }


    public DownloadStatus prepareDossierDownload(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                                 @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                                 @RequestBody DownloadRequest downloadRequest) {

        dossierTemplateController.getDossierTemplate(dossierTemplateId);

        var storageId = downloadController.prepareDownload(PrepareDownloadWithOptionRequest.builder()
                                                                   .dossierId(dossierId)
                                                                   .fileIds(statusController.getDossierStatus(dossierId)
                                                                                    .stream()
                                                                                    .map(FileStatus::getId)
                                                                                    .toList())
                                                                   .reportTemplateIds(downloadRequest.getReportTemplateIds())
                                                                   .downloadFileTypes(downloadRequest.getDownloadFileTypes())
                                                                   .redactionPreviewColor(downloadRequest.getRedactionPreviewColor())
                                                                   .includeUnprocessed(false)
                                                                   .build()).getStorageId();

        var status = downloadStatusPersistenceService.getStatus(storageId);

        return DownloadStatus.builder()
                .id(status.getUuid()) // This is a workaround the real is the storageId.
                .userId(status.getUserId())
                .filename(status.getFilename())
                .mimeType(status.getMimeType())
                .errorCause(status.getErrorCause())
                .status(status.getStatus())
                .creationDate(status.getCreationDate())
                .lastDownload(status.getLastDownload())
                .fileSize(status.getFileSize())
                .dossierId(status.getDossier().getId())
                .fileIds(status.getFiles()
                                 .stream()
                                 .map(FileEntity::getId)
                                 .toList())
                .downloadFileTypes(status.getDownloadFileTypes()
                                           .stream()
                                           .toList())
                .reportTemplateIds(downloadRequest.getReportTemplateIds())
                .build();
    }


    private DossierRequest mapToDossierRequest(String dossierTemplateId, DocuMineDossierRequest dossier) {

        return DossierRequest.builder()
                .dossierId(dossier.getId())
                .dossierName(dossier.getName())
                .dossierTemplateId(dossierTemplateId)
                .description(dossier.getDescription())
                .ownerId(dossier.getOwnerId())
                .memberIds(dossier.getMemberIds())
                .approverIds(currentApplicationTypeProvider.isDocuMine() ? dossier.getMemberIds() : dossier.getApproverIds()) // for DocuMine, the members are always set as approvers
                .downloadFileTypes(Set.of(DownloadFileType.ORIGINAL))
                .reportTemplateIds(dossier.getReportTemplateIds())
                .watermarkId(null)
                .previewWatermarkId(null)
                .dossierStatusId(dossier.getDossierStatusId())
                .build();
    }

}
