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

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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.springframework.security.acls.model.MutableAcl;
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.api.ICustomPermissionService;
import com.iqser.red.service.persistence.management.v1.processor.entity.permissions.CustomPermissionEntity;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.repository.CustomPermissionRepository;
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;

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

@Slf4j
@Service
@RequiredArgsConstructor
public class CustomPermissionService {

    private final CustomPermissionRepository customPermissionRepository;

    private final List<ICustomPermissionService<?, ?>> customPermissionServices;


    public void syncAllCustomPermissions() {

        log.info("Syncing all custom permissions");
        var targetObjects = getAllSupportedTargetObjects();
        targetObjects.forEach(this::applyCustomPermissions);
        log.info("All custom permissions synced");
    }


    public Set<String> getAllSupportedTargetObjects() {

        return customPermissionServices.stream().map(AbstractACLService::getIdentifier).collect(Collectors.toSet());
    }


    public void applyCustomPermissions(String targetObject) {

        var mappings = getCustomPermissionMappings(targetObject);
        apply(mappings);
    }


    public List<CustomPermissionMappingModel> getCustomPermissionMappings(String targetObject) {

        var all = customPermissionRepository.findByTargetObject(targetObject);

        Map<CustomPermissionModel, List<CustomPermissionModel>> mapping = new HashMap<>();

        for (var permission : all) {

            var targetPermission = RedPermission.resolvePermission(permission.getTargetPermissionMask());

            if (targetPermission.isEmpty()) {
                log.warn("Invalid saved permission: {}", permission.getTargetPermissionMask());
                continue;
            }

            var key = fromPermission(targetPermission.get());
            var value = RedPermission.resolvePermission(permission.getExistingPermissionMask()).map(this::fromPermission).orElse(EVERYONE_ELSE);

            var list = mapping.computeIfAbsent(key, entry -> new ArrayList<>());
            list.add(value);
        }

        var result = new ArrayList<CustomPermissionMappingModel>();
        mapping.forEach((k, v) -> result.add(new CustomPermissionMappingModel(k, v)));
        return result;
    }


    public void apply(List<CustomPermissionMappingModel> customPermissionMappingModels) {

        customPermissionMappingModels.forEach(p -> customPermissionServices.forEach(s -> {
            if (p.getTargetPermission().getMask() == s.forPermission().getMask()) {
                s.applyCustomPermissions(p);
            }
        }));
    }


    private CustomPermissionModel fromPermission(RedPermission redPermission) {

        return new CustomPermissionModel(redPermission.getMask(), redPermission.getName(), redPermission.getSort(), redPermission.isChangeable());
    }


    public void applyCustomPermissions(String targetObject, String objectId) {

        var mappings = getCustomPermissionMappings(targetObject);
        apply(mappings, objectId);
    }


    public void applyCustomPermissions(String targetObject, MutableAcl acl) {
        var mappings = getCustomPermissionMappings(targetObject);
        applyToAcl(mappings, acl);
    }

    public void applyToAcl(List<CustomPermissionMappingModel> customPermissionMappingModels, MutableAcl acl) {

        customPermissionMappingModels.forEach(p -> customPermissionServices.forEach(s -> {
            if (p.getTargetPermission().getMask() == s.forPermission().getMask()) {
                s.applyCustomPermissions(p, acl);
            }
        }));
    }

    public void apply(List<CustomPermissionMappingModel> customPermissionMappingModels, Serializable objectId) {

        customPermissionMappingModels.forEach(p -> customPermissionServices.forEach(s -> {
            if (p.getTargetPermission().getMask() == s.forPermission().getMask()) {
                s.applyCustomPermissions(p, objectId);
            }
        }));
    }


    public List<CustomPermissionModel> getTargetPermissions(String targetObject) {

        Set<CustomPermissionModel> permissionModels = new HashSet<>();

        customPermissionServices.forEach(service -> {
            if (service.getIdentifier().equals(targetObject)) {
                permissionModels.add(fromPermission(service.forPermission()));
            }
        });

        var result = new ArrayList<>(permissionModels);
        result.sort(Comparator.comparingInt(CustomPermissionModel::getSort));
        return result;
    }


    public List<CustomPermissionMappingModel> getExistingPermissions(String targetObject) {

        var result = new ArrayList<CustomPermissionMappingModel>();

        customPermissionServices.forEach(service -> {

            if (service.getIdentifier().equals(targetObject)) {

                CustomPermissionMappingModel cmp = new CustomPermissionMappingModel();
                List<CustomPermissionModel> permissionModels = new ArrayList<>();
                service.definingPermissions().forEach(dp -> permissionModels.add(fromPermission(dp)));

                if (service.allowEveryoneElseSetting()) {
                    permissionModels.add(EVERYONE_ELSE);
                }

                permissionModels.sort(Comparator.comparingInt(CustomPermissionModel::getSort));

                cmp.setMappedPermissions(permissionModels);
                cmp.setTargetPermission(fromPermission(service.forPermission()));

                result.add(cmp);
            }
        });

        result.sort(Comparator.comparingInt(a -> a.getTargetPermission().getSort()));
        return result;
    }


    @Transactional
    public void saveCustomPermissionMappings(String targetObject, List<CustomPermissionMappingModel> customPermissionMappingModel) {

        List<CustomPermissionEntity> entities = new ArrayList<>();
        // retrieve the defined configuration of permissions
        List<CustomPermissionMappingModel> existingPermissionDefined = getExistingPermissions(targetObject);

        customPermissionMappingModel.forEach(c -> c.getMappedPermissions().forEach(value -> {
            CustomPermissionEntity entity = new CustomPermissionEntity(targetObject, c.getTargetPermission().getMask(), value.getMask());
            entities.add(entity);
        }));

        for (CustomPermissionMappingModel permissionMappingModel : existingPermissionDefined) {
            CustomPermissionModel targetPermission = permissionMappingModel.getTargetPermission();
            List<CustomPermissionModel> mappedPermissionsDefined = permissionMappingModel.getMappedPermissions();
            //retrieve the permissions which can be changeable in order to add them
            List<Integer> mappedPermissionMasksNotChangeable = mappedPermissionsDefined.stream().filter(p -> !p.isChangeable()).map(CustomPermissionModel::getMask).toList();
            for (Integer mappedMask : mappedPermissionMasksNotChangeable) {
                Optional<CustomPermissionEntity> existingPermissionEntity = entities.stream()
                        .filter(e -> e.getTargetPermissionMask() == targetPermission.getMask() && e.getExistingPermissionMask() == mappedMask)
                        .findAny();
                if (existingPermissionEntity.isEmpty()) { //the required permission was disabled but it is not allowed
                    entities.add(new CustomPermissionEntity(targetObject, targetPermission.getMask(), mappedMask));
                }
            }
        }

        customPermissionRepository.deleteByTargetObject(targetObject);
        customPermissionRepository.saveAll(entities);

        this.apply(getCustomPermissionMappings(targetObject));
    }




}
