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

import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.acls.AclPermissionCacheOptimizer;
import org.springframework.security.acls.AclPermissionEvaluator;
import org.springframework.security.acls.domain.AclAuthorizationStrategy;
import org.springframework.security.acls.domain.ConsoleAuditLogger;
import org.springframework.security.acls.domain.DefaultPermissionFactory;
import org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy;
import org.springframework.security.acls.domain.PermissionFactory;
import org.springframework.security.acls.domain.SpringCacheBasedAclCache;
import org.springframework.security.acls.jdbc.BasicLookupStrategy;
import org.springframework.security.acls.jdbc.JdbcMutableAclService;
import org.springframework.security.acls.jdbc.LookupStrategy;
import org.springframework.security.acls.model.PermissionGrantingStrategy;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.knecon.fforesight.databasetenantcommons.providers.MultiTenantDataSource;

import lombok.RequiredArgsConstructor;

@Configuration
@RequiredArgsConstructor
@EnableTransactionManagement
public class ACLBeanConfiguration {

    private final MultiTenantDataSource dataSource;

    private final CacheManager cacheManager;

    private final static String ACL_CACHE = "acl";


    @Bean
    public AclAuthorizationStrategy aclAuthorizationStrategy() {

        return (acl, changeType) -> {
            // no-op
        };
    }


    @Bean
    public PermissionFactory permissionFactory() {

        return new DefaultPermissionFactory(RedPermission.class);
    }


    @Bean
    public PermissionGrantingStrategy permissionGrantingStrategy() {

        return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
    }


    @Bean
    public SpringCacheBasedAclCache aclCache() {

        return new SpringCacheBasedAclCache(cacheManager.getCache(ACL_CACHE), permissionGrantingStrategy(), aclAuthorizationStrategy());
    }


    @Bean
    public LookupStrategy lookupStrategy() {

        var lookupStrategy = new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
        lookupStrategy.setAclClassIdSupported(true);
        lookupStrategy.setPermissionFactory(permissionFactory());
        return lookupStrategy;
    }


    @Bean
    @Primary
    public MethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {

        DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
        AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService());
        permissionEvaluator.setPermissionFactory(permissionFactory());
        expressionHandler.setPermissionEvaluator(permissionEvaluator);

        var permissionCacheOptimizer = new AclPermissionCacheOptimizer(aclService());
        permissionCacheOptimizer.setObjectIdentityRetrievalStrategy(new RedObjectIdentityRetrievalStrategy());
        expressionHandler.setPermissionCacheOptimizer(permissionCacheOptimizer);
        return expressionHandler;
    }


    @Bean
    public JdbcMutableAclService aclService() {

        var service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
        service.setClassIdentityQuery("select currval(pg_get_serial_sequence('acl_class', 'id'))");
        service.setSidIdentityQuery("select currval(pg_get_serial_sequence('acl_sid', 'id'))");
        service.setAclClassIdSupported(true);
        return service;
    }

}
