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

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.GET_RSS;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierResource.DOSSIER_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.DossierTemplateResource.DOSSIER_TEMPLATE_ID_PARAM;
import static com.iqser.red.service.persistence.service.v2.api.external.resource.FileResource.FILE_ID_PARAM;

import java.util.ArrayList;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Value;
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.iqser.red.persistence.service.v1.external.api.impl.controller.DossierController;
import com.iqser.red.persistence.service.v1.external.api.impl.controller.StatusController;
import com.iqser.red.persistence.service.v2.external.api.impl.mapper.ComponentMapper;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.roles.ApplicationRoles;
import com.iqser.red.service.persistence.management.v1.processor.service.AccessControlService;
import com.iqser.red.service.persistence.management.v1.processor.service.ComponentLogService;
import com.iqser.red.service.persistence.management.v1.processor.service.CurrentApplicationTypeProvider;
import com.iqser.red.service.persistence.management.v1.processor.service.FileStatusService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.DossierTemplatePersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
import com.iqser.red.service.persistence.service.v1.api.shared.model.analysislog.componentlog.ComponentLogEntry;
import com.iqser.red.service.persistence.service.v1.api.shared.model.component.RevertOverrideRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.file.FileModel;
import com.iqser.red.service.persistence.service.v2.api.external.model.BulkComponentsRequest;
import com.iqser.red.service.persistence.service.v2.api.external.model.Component;
import com.iqser.red.service.persistence.service.v2.api.external.model.ComponentOverrideList;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponents;
import com.iqser.red.service.persistence.service.v2.api.external.model.FileComponentsList;
import com.iqser.red.service.persistence.service.v2.api.external.resource.ComponentResource;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.knecon.fforesight.tenantcommons.TenantProvider;

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 = "4. Component endpoints", description = "Provides operations related to components")
public class ComponentControllerV2 implements ComponentResource {

    private final AccessControlService accessControlService;
    private final ComponentLogService componentLogService;
    private final UserService userService;
    private final StatusController statusController;
    private final FileStatusService fileStatusService;
    private final DossierController dossierController;
    private final DossierTemplatePersistenceService dossierTemplatePersistenceService;
    private final CurrentApplicationTypeProvider currentApplicationTypeProvider;
    private final ComponentMapper componentMapper = ComponentMapper.INSTANCE;


    @Value("${documine.components.filesLimit:100}")
    private int documineComponentsFilesLimit = 100;


    @Override
    public FileComponents getComponents(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                        @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                        @PathVariable(FILE_ID_PARAM) String fileId,
                                        @RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails) {

        checkApplicationType();
        validateUserRoles(KeycloakSecurity.getUserId());
        dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
        var componentLog = componentLogService.getComponentLog(dossierId, fileId);

        return componentMapper.toFileComponents(componentLog, dossierTemplateId, dossierId, fileId, fileStatusService.getFileName(fileId), includeDetails);
    }


    private void validateUserRoles(String userId) {

        Optional<User> userOptional = userService.getUserById(userId);
        if (userOptional.isPresent()) {
            if (userOptional.get().getRoles()
                    .stream()
                    .noneMatch(ApplicationRoles.VALID_MEMBER_ROLES::contains)) {
                throw new NotAllowedException("User doesn't have appropriate roles");
            }
        }
    }


    @Override
    public FileComponentsList getComponentsOfDossier(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                                     @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                                     @RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails) {

        checkApplicationType();
        dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
        var dossierFiles = statusController.getDossierStatus(dossierId);

        if(dossierFiles.size() > documineComponentsFilesLimit) {
            throw new BadRequestException(String.format("The dossier you requested components for contains %s files this is above the limit of %s files for this endpoint, please use the POST %s", dossierFiles.size(), documineComponentsFilesLimit, FILE_PATH + BULK_COMPONENTS_PATH));
        }

        return new FileComponentsList(dossierFiles.stream()
                                              .map(file -> getComponents(dossierTemplateId, dossierId, file.getFileId(), includeDetails))
                                              .toList());
    }


    @Override
    public FileComponentsList getComponentsForFiles(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                             @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                             @RequestParam(name = INCLUDE_DETAILS_PARAM, defaultValue = "false", required = false) boolean includeDetails,
                                             @RequestBody BulkComponentsRequest bulkComponentsRequest){

        if(bulkComponentsRequest.getFileIds().size() > documineComponentsFilesLimit) {
            throw new BadRequestException(String.format("You requested components for %s files this is above the limit of %s files for this endpoint, lower the fileIds in the request", bulkComponentsRequest.getFileIds().size(), documineComponentsFilesLimit));
        }

        checkApplicationType();
        dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);
        dossierController.getDossier(dossierId, false, false);
        return new FileComponentsList(bulkComponentsRequest.getFileIds().stream()
                                              .map(fileId -> getComponents(dossierTemplateId, dossierId, fileId, includeDetails))
                                              .toList());

    }


    @Override
    @PreAuthorize("hasAuthority('" + GET_RSS + "')")
    public void addOverride(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                            @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                            @PathVariable(FILE_ID_PARAM) String fileId,
                            @RequestBody Component override) {

        checkApplicationType();
        accessControlService.verifyUserIsReviewer(dossierId, fileId);
        dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);

        componentLogService.overrideComponent(dossierId, fileId, componentMapper.toComponentLogEntry(override));
    }


    @Override
    @PreAuthorize("hasAuthority('" + GET_RSS + "')")
    public ComponentOverrideList getOverrides(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                              @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                              @PathVariable(FILE_ID_PARAM) String fileId) {

        checkApplicationType();
        dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);

        FileModel status = fileStatusService.getStatus(fileId);

        var overrides = componentLogService.getComponentLog(dossierId, fileId).getComponentLogEntries()
                .stream()
                .filter(ComponentLogEntry::isOverridden)
                .collect(Collectors.toList());
        var componentOverrides = componentMapper.toComponents(overrides);

        return ComponentOverrideList.builder()
                .dossierTemplateId(dossierTemplateId)
                .dossierId(dossierId)
                .fileId(fileId)
                .componentOverrides(status.isSoftOrHardDeleted() ? new ArrayList<>() : componentOverrides)
                .build();
    }


    @Override
    @PreAuthorize("hasAuthority('" + GET_RSS + "')")
    public void revertOverrides(@PathVariable(DOSSIER_TEMPLATE_ID_PARAM) String dossierTemplateId,
                                @PathVariable(DOSSIER_ID_PARAM) String dossierId,
                                @PathVariable(FILE_ID_PARAM) String fileId,
                                @RequestBody RevertOverrideRequest revertOverrideRequest) {

        checkApplicationType();
        accessControlService.verifyUserIsReviewer(dossierId, fileId);
        dossierTemplatePersistenceService.checkDossierTemplateExistsOrElseThrow404(dossierTemplateId);

        componentLogService.revertOverrides(dossierId, fileId, revertOverrideRequest);
    }


    private void checkApplicationType() {

        if (!currentApplicationTypeProvider.isDocuMine()) {
            throw new NotAllowedException("Components can only be accessed in DocuMine");
        }
    }

}
