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

import java.util.Set;

import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

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.DossierNotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotAllowedException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.users.UserService;
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.WorkflowStatus;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class AccessControlService {

    private final FileStatusManagementService fileStatusManagementService;
    private final UserService userService;
    private final DossierManagementService dossierManagementService;
    private final DossierACLService dossierACLService;
    private final AclPermissionEvaluator aclPermissionEvaluator;


    public void verifyUserIsReviewer(String dossierId, String fileId) {

        String fileReviewer = getAssignee(dossierId, fileId);

        var isReviewer = KeycloakSecurity.getUserId().equals(fileReviewer);
        if (!isReviewer) {
            throw new NotAllowedException("User must be reviewer of the file.");
        }
    }


    private String getAssignee(String dossierId, String fileId) {

        try {
            return fileStatusManagementService.getFileStatus(fileId).getAssignee();
        } catch (FeignException e) {
            if (e.status() == HttpStatus.NOT_FOUND.value()) {
                throw new NotFoundException("File " + fileId + " dossier " + dossierId + " not found.");
            }
            throw e;
        }
    }


    public void verifyUserIsReviewerOrApprover(String dossierId, String fileId) {

        var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));

        String fileReviewer = getAssignee(dossierId, fileId);

        var isReviewer = KeycloakSecurity.getUserId().equals(fileReviewer);
        var isApprover = dossier.getApproverIds().contains(KeycloakSecurity.getUserId());
        if (!isReviewer && !isApprover) {
            throw new NotAllowedException("User must be reviewer or approver.");
        }
    }


    public void verifyUserIsMemberOrApprover(String dossierId, String userId) {

        var dossier = dossierACLService.enhanceDossierWithACLData(dossierManagementService.getDossierById(dossierId, false, false));

        var isMember = dossier.getMemberIds().contains(userId);
        var isApprover = dossier.getApproverIds().contains(userId);
        if (!isMember && !isApprover) {
            throw new BadRequestException("User must be dossier member or approver.");
        }
    }


    // checks that the user has view permissions to dossier and returns 403 if it doesn't
    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'VIEW_OBJECT')")
    public void verifyUserHasViewPermissions(String dossierId) {

    }


    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'ACCESS_OBJECT')")
    public void verifyUserHasAccessPermissions(String dossierId) {

    }


    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'APPROVE') || hasPermission(#dossierId, 'Dossier', 'REVIEW')")
    public void verifyUserIsMemberOrApprover(String dossierId) {

    }


    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'APPROVE')")
    public void verifyUserIsApprover(String dossierId) {

    }


    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'OWNER')")
    public void verifyUserIsDossierOwner(String dossierId) {

    }


    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'WRITE')")
    public void verifyWriter(String dossierId) {

    }


    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'APPROVE') || hasPermission(#dossierId, 'Dossier', 'OWNER')")
    public void verifyUserIsDossierOwnerOrApprover(String dossierId) {

    }


    // checks that the user has view permissions to dossier and returns a boolean flag
    public boolean hasUserViewPermissionsForDossier(String dossierId) {

        return aclPermissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), dossierId, "Dossier", "VIEW_OBJECT");
    }


    public boolean hasUserAuthority(String authority) {

        return SecurityContextHolder.getContext().getAuthentication().getAuthorities()
                .stream()
                .anyMatch(a -> a.getAuthority().equals(authority));
    }


    public void verifyFileIsNotApproved(String dossierId, String fileId) {

        try {
            FileModel fileStatus = fileStatusManagementService.getFileStatus(fileId, false);

            if(!fileStatus.getDossierId().equals(dossierId)) {
                throw new NotFoundException("File " + fileId + " dossier " + dossierId + " not found.");
            }

            var status = fileStatus.getWorkflowStatus();
            if (WorkflowStatus.APPROVED.equals(status)) {
                throw new NotAllowedException("File is already in status APPROVED.");
            }
        } catch (FeignException e) {
            if (e.status() == HttpStatus.NOT_FOUND.value()) {
                throw new NotFoundException("File " + fileId + " dossier " + dossierId + " not found.");
            }
            throw e;
        }
    }


    //verifies that user has view permissions to the dossier and responds with 404 if it doesn't
    public void checkViewPermissionsToDossier(String dossierId) {

        if (!hasUserViewPermissionsForDossier(dossierId)) {
            throw new DossierNotFoundException(DossierNotFoundException.DOSSIER_NOT_FOUND_MESSAGE);
        }
    }


    //verifies that dossier is present and not deleted and user has view permissions to the dossier and responds with 404 if it doesn't
    public void checkDossierExistenceAndViewPermissionsToDossier(String dossierId) {

        dossierManagementService.getDossierById(dossierId, true, false);
        checkViewPermissionsToDossier(dossierId);
    }


    //verifies that user has access permissions to the dossier and responds with 403 in case it doesn't
    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'ACCESS_OBJECT')")
    public void checkAccessPermissionsToDossier(String dossierId) {

        checkViewPermissionsToDossier(dossierId);
    }


    //checks the existence of dossier and if it is not deleted and view permissions
    @PostAuthorize("hasPermission(#dossierId, 'Dossier', 'ACCESS_OBJECT')")
    public void checkDossierExistenceAndAccessPermissionsToDossier(String dossierId) {

        checkDossierExistenceAndViewPermissionsToDossier(dossierId);
    }


    public void validateFileResourceExistence(String fileId) {

        var status = fileStatusManagementService.getFileStatus(fileId);
        if (status.isSoftOrHardDeleted()) {
            throw new NotFoundException("File not found");
        }
    }

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

        try {
            verifyUserIsDossierOwnerOrApprover(dossierId);
        } catch (AccessDeniedException e1) {
            try {
                for (String fileId : fileIds) {
                    verifyUserIsReviewer(dossierId, fileId);
                }
            } catch (NotAllowedException e2) {
                throw new NotAllowedException("User must be dossier owner, approver or assigned reviewer of the file.");
            }
        }
    }

}
