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

import static com.iqser.red.service.persistence.management.v1.processor.acl.custom.constants.CustomPermissionConstants.EVERYONE_ELSE;

import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.security.acls.domain.AbstractPermission;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
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.client.tenantusermanagementservice.UsersClient;
import com.iqser.red.service.persistence.management.v1.processor.service.users.model.User;
import com.iqser.red.service.persistence.service.v1.api.shared.model.permission.CustomPermissionMappingModel;
import com.iqser.red.service.persistence.service.v1.api.shared.model.permission.CustomPermissionModel;

public abstract class ICustomPermissionService<T, ID extends Serializable> extends AbstractACLService<ID> {

    protected final UsersClient usersClient;


    protected ICustomPermissionService(UsersClient usersClient, MutableAclService mutableAclService) {

        super(mutableAclService);
        this.usersClient = usersClient;
    }


    @Transactional
    public void applyCustomPermissions(CustomPermissionMappingModel customPermissionMappingModel) {

        var objectIds = provideAllObjectIds();
        objectIds.forEach(id -> applyCustomPermissions(customPermissionMappingModel, id));
    }


    protected abstract Collection<ID> provideAllObjectIds();


    @Transactional
    public void applyCustomPermissions(CustomPermissionMappingModel customPermissionMappingModel, Serializable objectId) {

        var acl = getOrCreateACL(objectId);
        applyAces(acl, customPermissionMappingModel);
        mutableAclService.updateAcl(acl);
    }


    public void applyCustomPermissions(CustomPermissionMappingModel customPermissionMappingModel, MutableAcl acl){
        applyAces(acl, customPermissionMappingModel);
    }


    protected void applyAces(MutableAcl acl, CustomPermissionMappingModel model) {

        clearAces(acl);

        var redPermission = RedPermission.resolvePermission(model.getTargetPermission().getMask());

        if (redPermission.isPresent()) {
            var masks = model.getMappedPermissions().stream().map(CustomPermissionModel::getMask).collect(Collectors.toSet());

            var principalsForCustomPermission = getSidsForMasks(acl, masks);

            if (allowEveryoneElseSetting() && masks.contains(EVERYONE_ELSE.getMask())) {
                var allUserIds = getUserIds();
                var usersWithDefinedPermissions = getSidsForMasks(acl, definingPermissions().stream().map(AbstractPermission::getMask).collect(Collectors.toSet()));
                allUserIds.removeAll(usersWithDefinedPermissions);

                principalsForCustomPermission.addAll(allUserIds);
            }

            principalsForCustomPermission.forEach(p -> acl.insertAce(0, redPermission.get(), new PrincipalSid(p), true));
        }
    }


    protected void clearAces(MutableAcl acl) {

        for (var i = acl.getEntries().size() - 1; i >= 0; i--) {
            if (acl.getEntries().get(i).getPermission().getMask() == forPermission().getMask()) {
                acl.deleteAce(i);
            }
        }
    }


    private Set<String> getSidsForMasks(MutableAcl acl, Collection<Integer> masks) {

        return acl.getEntries()
                .stream()
                .filter(e -> masks.contains(e.getPermission().getMask()))
                .map(AccessControlEntry::getSid)
                .filter(PrincipalSid.class::isInstance)
                .map(s -> ((PrincipalSid) s).getPrincipal())
                .collect(Collectors.toSet());

    }


    public boolean allowEveryoneElseSetting() {

        return true;
    }


    private Set<String> getUserIds() {

        return usersClient.getAllUsers(true).stream().map(User::getUserId).collect(Collectors.toSet());
    }


    public abstract Set<RedPermission> definingPermissions();


    public abstract RedPermission forPermission();

}
