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

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_DOSSIER_TEMPLATES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_DOSSIER_TEMPLATES;
import static com.iqser.red.service.persistence.management.v1.processor.service.FeignExceptionHandler.processFeignException;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
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.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier.DossierACLService;
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.DossierManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateManagementService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierTemplateStatsService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DossierTemplateResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DossierTemplateModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.DownloadResponse;
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.CloneDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.CreateOrUpdateDossierTemplateRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplate;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStats;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.DossierTemplateStatus;
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.importexport.ExportDownloadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.importexport.ImportDossierTemplateRequest;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

import feign.FeignException;
import io.micrometer.core.annotation.Timed;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequiredArgsConstructor
public class DossierTemplateController implements DossierTemplateResource {

    private final DossierTemplateManagementService dossierTemplateManagementService;
    private final DossierTemplateStatsService dossierTemplateStatsService;
    private final AuditPersistenceService auditPersistenceService;
    private final DossierManagementService dossierManagementService;
    private final DossierACLService dossierACLService;
    private final UserService userService;


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
    public DossierTemplateModel createOrUpdateDossierTemplate(@RequestBody DossierTemplateModel dossierTemplateModel) {

        String userId = KeycloakSecurity.getUserId();
        dossierTemplateModel.setCreatedBy(userId);
        dossierTemplateModel.setModifiedBy(userId);
        var dossierTemplate = new CreateOrUpdateDossierTemplateRequest();
        BeanUtils.copyProperties(dossierTemplateModel, dossierTemplate);
        try {
            DossierTemplateModel response = convert(dossierTemplateManagementService.createOrUpdateDossierTemplate(dossierTemplate));

            auditPersistenceService.audit(AuditRequest.builder()
                    .userId(KeycloakSecurity.getUserId())
                    .objectId(response.getDossierTemplateId())
                    .category(AuditCategory.DOSSIER_TEMPLATE.name())
                    .message("Dossier Template has been added or updated")
                    .build());
            return response;
        } catch (FeignException e) {
            throw processFeignException(e);
        }

    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
    public List<DossierTemplateModel> getAllDossierTemplates() {

        return dossierTemplateManagementService.getAllDossierTemplates().stream().map(this::convert).collect(Collectors.toList());
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
    public DossierTemplateModel getDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {

        try {
            return convert(dossierTemplateManagementService.getDossierTemplate(dossierTemplateId));
        } catch (FeignException e) {
            throw processFeignException(e);
        }
    }


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
    public void deleteDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {

        String userId = KeycloakSecurity.getUserId();

        List<Dossier> dossiers = dossierManagementService.getAllDossiers(true, false);
        if (dossiers != null && dossiers.stream().anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
            throw new ConflictException("Can not delete dossier template because there are dossiers based on it");
        }

        dossierTemplateManagementService.deleteDossierTemplate(dossierTemplateId, userId);
        auditPersistenceService.audit(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DOSSIER_TEMPLATE.name())
                .message("Dossier Template has been deleted")
                .build());
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
    public void deleteDossierTemplates(@RequestBody List<String> dossierTemplateIds) {

        String userId = KeycloakSecurity.getUserId();
        List<String> errorIds = new ArrayList<>();

        for (String dossierTemplateId : dossierTemplateIds) {
            try {
                List<Dossier> dossiers = dossierManagementService.getAllDossiers(true, false);
                if (dossiers != null && dossiers.stream().anyMatch(dossier -> dossier.getDossierTemplateId().equals(dossierTemplateId))) {
                    throw new ConflictException("Can not delete dossier template because there are dossiers based on it");
                }

                dossierTemplateManagementService.deleteDossierTemplate(dossierTemplateId, userId);
                auditPersistenceService.audit(AuditRequest.builder()
                        .userId(KeycloakSecurity.getUserId())
                        .objectId(dossierTemplateId)
                        .category(AuditCategory.DOSSIER_TEMPLATE.name())
                        .message("Dossier template has been deleted")
                        .build());
            } catch (FeignException e) {
                errorIds.add(dossierTemplateId);
            }
        }

        if (!errorIds.isEmpty()) {
            throw new BadRequestException("Failed to delete dossier templates with ids: " + errorIds);
        }
    }


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
    public DossierTemplateModel cloneDossierTemplate(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId,
                                                     @RequestBody CloneDossierTemplateRequest cloneDossierTemplateRequest) {

        String userId = KeycloakSecurity.getUserId();

        try {

            DossierTemplateModel response = convert(dossierTemplateManagementService.cloneDossierTemplate(dossierTemplateId, cloneDossierTemplateRequest));
            auditPersistenceService.audit(AuditRequest.builder()
                    .userId(userId)
                    .objectId(response.getDossierTemplateId())
                    .category(AuditCategory.DOSSIER_TEMPLATE.name())
                    .message("Dossier Template has been cloned")
                    .build());
            return response;
        } catch (FeignException e) {
            throw processFeignException(e);
        }
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
    public DossierTemplateStats getDossierTemplateStats(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {

        try {
            var stats = dossierTemplateStatsService.getDossierTemplateStats(dossierTemplateId);
            enhanceDossierTemplateStatsWithACLMemberDetails(stats);
            return stats;
        } catch (FeignException e) {
            throw processFeignException(e);
        }
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
    public List<DossierTemplateStats> getDossierTemplateStats() {

        try {
            var statsList = dossierTemplateStatsService.getDossierTemplateStats();
            statsList.forEach(this::enhanceDossierTemplateStatsWithACLMemberDetails);
            return statsList;
        } catch (FeignException e) {
            throw processFeignException(e);
        }
    }


    @PreAuthorize("hasAuthority('" + READ_DOSSIER_TEMPLATES + "')")
    public DownloadResponse prepareExportDownload(@PathVariable(DOSSIER_TEMPLATE_ID) String dossierTemplateId) {

        try {
            ExportDownloadRequest request = ExportDownloadRequest.builder().dossierTemplateId(dossierTemplateId).userId(KeycloakSecurity.getUserId()).build();
            var response = dossierTemplateManagementService.prepareExportDownload(request);
            auditPersistenceService.audit(AuditRequest.builder()
                    .userId(KeycloakSecurity.getUserId())
                    .objectId(response.getValue())
                    .category(AuditCategory.DOWNLOAD.name())
                    .message("Export Download was prepared")
                    .details(Map.of("dossierTemplateId", request.getDossierTemplateId()))
                    .build());
            return new DownloadResponse(response.getValue());
        } catch (FeignException e) {
            throw processFeignException(e);
        }
    }


    @Timed
    @PreAuthorize("hasAuthority('" + WRITE_DOSSIER_TEMPLATES + "')")
    public DossierTemplateModel importDossierTemplate(@RequestPart(name = "file") MultipartFile file,
                                                      @RequestParam(value = DOSSIER_TEMPLATE_ID, required = false) String dossierTemplateId,
                                                      @RequestParam(value = "updateExistingDossierTemplate", required = false, defaultValue = "false") boolean updateExistingDossierTemplate) {

        String originalFileName = file.getOriginalFilename();
        if (originalFileName == null || originalFileName.isEmpty()) {
            throw new BadRequestException("Could not upload file, no filename provided.");
        }
        var extension = originalFileName.substring(originalFileName.lastIndexOf(".") + 1).toLowerCase();
        if ("zip".equalsIgnoreCase(extension)) {
            if (StringUtils.isEmpty(dossierTemplateId) && updateExistingDossierTemplate) {
                throw new BadRequestException("Could not update with dossier template empty");
            }
            try {
                if (dossierTemplateId != null && updateExistingDossierTemplate) {
                    dossierTemplateManagementService.getDossierTemplate(dossierTemplateId); //check if the dossierTemplate to update exists
                }

                ImportDossierTemplateRequest request = ImportDossierTemplateRequest.builder()
                        .dossierTemplateId(dossierTemplateId)
                        .updateExistingDossierTemplate(updateExistingDossierTemplate)
                        .userId(KeycloakSecurity.getUserId())
                        .archive(file.getBytes())
                        .build();
                DossierTemplate loadedDossierTemplate = dossierTemplateManagementService.importDossierTemplate(request);
                auditPersistenceService.audit(AuditRequest.builder()
                        .userId(KeycloakSecurity.getUserId())
                        .objectId(loadedDossierTemplate.getId())
                        .category(AuditCategory.DOSSIER_TEMPLATE.name())
                        .message("Dossier template was imported")
                        .details(Map.of("dossierTemplateId", loadedDossierTemplate.getId()))
                        .build());
                return convert(loadedDossierTemplate);
            } catch (IOException e) {
                throw new BadRequestException(e.getMessage(), e);
            } catch (FeignException e) {
                throw processFeignException(e);
            }
        } else {
            throw new BadRequestException("Invalid extension");
        }
    }


    private void enhanceDossierTemplateStatsWithACLMemberDetails(DossierTemplateStats stats) {

        Set<String> members = new HashSet<>();
        stats.getDossiersInTemplate().forEach(d -> members.addAll(dossierACLService.getMembers(d)));
        stats.setNumberOfPeople(members.size());
    }


    private DossierTemplateModel convert(DossierTemplate dossierTemplate) {

        return DossierTemplateModel.builder()
                .dossierTemplateId(dossierTemplate.getId())
                .name(dossierTemplate.getName())
                .description(dossierTemplate.getDescription())
                .dateAdded(dossierTemplate.getDateAdded())
                .dateModified(dossierTemplate.getDateModified())
                .createdBy(dossierTemplate.getCreatedBy())
                .modifiedBy(dossierTemplate.getModifiedBy())
                .validFrom(dossierTemplate.getValidFrom())
                .validTo(dossierTemplate.getValidTo())
                .downloadFileTypes(dossierTemplate.getDownloadFileTypes())
                .dossierTemplateStatus(DossierTemplateStatus.valueOf(dossierTemplate.getDossierTemplateStatus().name()))
                .keepImageMetadata(dossierTemplate.isKeepImageMetadata())
                .keepHiddenText(dossierTemplate.isKeepHiddenText())
                .keepOverlappingObjects(dossierTemplate.isKeepOverlappingObjects())
                .applyDictionaryUpdatesToAllDossiersByDefault(dossierTemplate.isApplyDictionaryUpdatesToAllDossiersByDefault())
                .ocrByDefault(dossierTemplate.isOcrByDefault())
                .removeWatermark(dossierTemplate.isRemoveWatermark())
                .build();
    }

}
