package com.iqser.red.service.persistence.management.v1.processor.acl.custom.dossier;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.iqser.red.service.persistence.management.v1.processor.acl.AbstractACLService;
import com.iqser.red.service.persistence.management.v1.processor.acl.RedPermission;
import com.iqser.red.service.persistence.management.v1.processor.acl.custom.service.CustomPermissionService;
import com.iqser.red.service.persistence.management.v1.processor.service.DossierManagementService;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.dossier.Dossier;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
public class DossierACLService extends AbstractACLService<String> {

    private final CustomPermissionService customPermissionService;

    private final DossierManagementService dossierManagementService;


    public DossierACLService(MutableAclService mutableAclService, DossierManagementService dossierManagementService, CustomPermissionService customPermissionService) {

        super(mutableAclService);
        this.dossierManagementService = dossierManagementService;
        this.customPermissionService = customPermissionService;
    }


    @PostFilter("hasPermission(filterObject, 'Dossier', 'VIEW_OBJECT')")
    public List<String> getDossierIdsWithViewPermission() {

        return dossierManagementService.getAllDossiers(true, true).stream().map(Dossier::getId).collect(Collectors.toList());
    }


    public Set<String> getMembers(String dossierId) {

        ObjectIdentityImpl dossierIdentity = new ObjectIdentityImpl(getIdentifier(), dossierId);
        var acl = mutableAclService.readAclById(dossierIdentity);
        Set<String> members = new HashSet<>();
        acl.getEntries().forEach(entry -> {
            if (entry.getSid() instanceof PrincipalSid) {
                var principal = ((PrincipalSid) entry.getSid()).getPrincipal();
                if (entry.getPermission().getMask() == RedPermission.APPROVE.getMask()) {
                    members.add(principal);
                }
                if (entry.getPermission().getMask() == RedPermission.REVIEW.getMask()) {
                    members.add(principal);
                }
                if (entry.getPermission().getMask() == RedPermission.OWNER.getMask()) {
                    members.add(principal);
                }
            }
        });
        return members;
    }


    @Override
    public String getIdentifier() {

        return "Dossier";
    }


    public Dossier enhanceDossierWithACLData(Dossier dossier) {

        ObjectIdentityImpl dossierIdentity = new ObjectIdentityImpl(getIdentifier(), dossier.getId());
        var acl = mutableAclService.readAclById(dossierIdentity);
        dossier.setOwnerId(null);
        dossier.getMemberIds().clear();
        dossier.getApproverIds().clear();
        acl.getEntries().forEach(entry -> {
            if (entry.getSid() instanceof PrincipalSid) {
                var principal = ((PrincipalSid) entry.getSid()).getPrincipal();
                if (entry.getPermission().getMask() == RedPermission.APPROVE.getMask()) {
                    dossier.getApproverIds().add(principal);
                    dossier.getMemberIds().add(principal);
                }
                if (entry.getPermission().getMask() == RedPermission.REVIEW.getMask()) {
                    dossier.getMemberIds().add(principal);
                }
                if (entry.getPermission().getMask() == RedPermission.OWNER.getMask()) {
                    dossier.getMemberIds().add(principal);
                    dossier.setOwnerId(principal);
                }
            }
        });
        return dossier;
    }


    @Transactional
    public void updateDossierACL(Set<String> members, Set<String> approvers, String owner, String dossierId) {

        var acl = getOrCreateACL(dossierId);

        // naive clear all ace's first
        while (!acl.getEntries().isEmpty()) {
            try {
                acl.deleteAce(0);
            } catch (NotFoundException e) {
                break;
            }
        }

        log.info("Setting members and approvers to ACL for dossier: {}", dossierId);

        if (owner != null) {
            acl.insertAce(0, RedPermission.OWNER, new PrincipalSid(owner), true);
        }

        members.forEach(member -> acl.insertAce(0, RedPermission.REVIEW, new PrincipalSid(member), true));
        approvers.forEach(member -> acl.insertAce(0, RedPermission.APPROVE, new PrincipalSid(member), true));

        customPermissionService.applyCustomPermissions(getIdentifier(), acl);

        mutableAclService.updateAcl(acl);

    }

}
