package tech.easyflow.ai.chattime.availability;

import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.permission.KnowledgeVisibilityQueryHelper;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.system.entity.vo.RoleCategoryAccessSnapshot;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.SysDeptService;

import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;

/**
 * {@link ChatTimeKnowledgeAvailabilityResolver} 单元测试。
 *
 * @author Codex
 * @since 2026-05-10
 */
public class ChatTimeKnowledgeAvailabilityResolverTest {

    /**
     * 创建者应始终可访问自己的私有知识库。
     */
    @Test
    public void resolveShouldAllowCreatorForPrivateKnowledge() {
        LoginAccount loginAccount = buildLoginAccount(11, 3);
        ChatTimeKnowledgeAvailabilityResolver resolver = buildResolver(
                new RoleCategoryAccessSnapshot("KNOWLEDGE", BigInteger.valueOf(11), false, false, Collections.emptySet()),
                setOf(BigInteger.valueOf(3))
        );

        ChatTimeToolAvailabilityDecision decision = resolver.resolve(
                buildContext(loginAccount),
                buildKnowledge(11, 21, null, "PRIVATE")
        );

        Assert.assertTrue(decision.isAvailable());
    }

    /**
     * 分类权限不通过时，即使知识库是公开范围也不可访问。
     */
    @Test
    public void resolveShouldRejectWhenCategoryPermissionFails() {
        LoginAccount loginAccount = buildLoginAccount(12, 3);
        ChatTimeKnowledgeAvailabilityResolver resolver = buildResolver(
                new RoleCategoryAccessSnapshot("KNOWLEDGE", BigInteger.valueOf(12), false, false, setOf(BigInteger.valueOf(99))),
                setOf(BigInteger.valueOf(3))
        );

        ChatTimeToolAvailabilityDecision decision = resolver.resolve(
                buildContext(loginAccount),
                buildKnowledge(11, 21, null, "PUBLIC")
        );

        Assert.assertFalse(decision.isAvailable());
        Assert.assertEquals("CHAT_TIME_KNOWLEDGE_FORBIDDEN", decision.getReasonCode());
    }

    /**
     * 同部门或祖先部门命中时，应允许访问部门可见知识库。
     */
    @Test
    public void resolveShouldAllowDeptScopedKnowledgeForReadableDept() {
        LoginAccount loginAccount = buildLoginAccount(12, 9);
        ChatTimeKnowledgeAvailabilityResolver resolver = buildResolver(
                new RoleCategoryAccessSnapshot("KNOWLEDGE", BigInteger.valueOf(12), false, false, setOf(BigInteger.valueOf(21))),
                setOf(BigInteger.valueOf(1), BigInteger.valueOf(3), BigInteger.valueOf(9))
        );

        ChatTimeToolAvailabilityDecision decision = resolver.resolve(
                buildContext(loginAccount),
                buildKnowledge(11, 21, BigInteger.valueOf(3), "DEPT")
        );

        Assert.assertTrue(decision.isAvailable());
    }

    /**
     * 超级管理员始终可访问。
     */
    @Test
    public void resolveShouldAllowSuperAdmin() {
        LoginAccount loginAccount = buildLoginAccount(99, 1);
        ChatTimeKnowledgeAvailabilityResolver resolver = buildResolver(
                new RoleCategoryAccessSnapshot("KNOWLEDGE", BigInteger.valueOf(99), true, true, Collections.emptySet()),
                Collections.emptySet()
        );

        ChatTimeToolAvailabilityDecision decision = resolver.resolve(
                buildContext(loginAccount),
                buildKnowledge(11, 21, null, "PRIVATE")
        );

        Assert.assertTrue(decision.isAvailable());
    }

    private ChatTimeKnowledgeAvailabilityResolver buildResolver(RoleCategoryAccessSnapshot accessSnapshot,
                                                                Set<BigInteger> deptIds) {
        return new ChatTimeKnowledgeAvailabilityResolver(
                new KnowledgeVisibilityQueryHelper(),
                mockCategoryPermissionService(accessSnapshot),
                mockSysDeptService(deptIds)
        );
    }

    private ChatTimeToolAvailabilityContext buildContext(LoginAccount loginAccount) {
        ChatTimeToolAvailabilityContext context = new ChatTimeToolAvailabilityContext();
        context.setLoginAccount(loginAccount);
        return context;
    }

    private LoginAccount buildLoginAccount(long accountId, long deptId) {
        LoginAccount loginAccount = new LoginAccount();
        loginAccount.setId(BigInteger.valueOf(accountId));
        loginAccount.setDeptId(BigInteger.valueOf(deptId));
        return loginAccount;
    }

    private DocumentCollection buildKnowledge(long createdBy, long categoryId, BigInteger deptId, String visibilityScope) {
        DocumentCollection knowledge = new DocumentCollection();
        knowledge.setId(BigInteger.valueOf(101));
        knowledge.setCreatedBy(BigInteger.valueOf(createdBy));
        knowledge.setCategoryId(BigInteger.valueOf(categoryId));
        knowledge.setDeptId(deptId);
        knowledge.setVisibilityScope(visibilityScope);
        return knowledge;
    }

    private CategoryPermissionService mockCategoryPermissionService(RoleCategoryAccessSnapshot accessSnapshot) {
        return (CategoryPermissionService) Proxy.newProxyInstance(
                CategoryPermissionService.class.getClassLoader(),
                new Class<?>[]{CategoryPermissionService.class},
                (proxy, method, args) -> {
                    if ("getAccess".equals(method.getName())) {
                        return accessSnapshot;
                    }
                    if ("isSuperAdmin".equals(method.getName())) {
                        return accessSnapshot.isSuperAdmin();
                    }
                    return defaultValue(method.getReturnType());
                }
        );
    }

    private SysDeptService mockSysDeptService(Set<BigInteger> deptIds) {
        Set<BigInteger> readableDeptIds = deptIds == null ? Collections.emptySet() : deptIds;
        return (SysDeptService) Proxy.newProxyInstance(
                SysDeptService.class.getClassLoader(),
                new Class<?>[]{SysDeptService.class},
                (proxy, method, args) -> {
                    if ("getSelfAndAncestorDeptIds".equals(method.getName())) {
                        return readableDeptIds;
                    }
                    if ("canUserAccessDeptScopedResource".equals(method.getName())) {
                        BigInteger resourceDeptId = (BigInteger) args[1];
                        return readableDeptIds.contains(resourceDeptId);
                    }
                    return defaultValue(method.getReturnType());
                }
        );
    }

    private Set<BigInteger> setOf(BigInteger... values) {
        Set<BigInteger> result = new LinkedHashSet<>();
        Collections.addAll(result, values);
        return result;
    }

    private Object defaultValue(Class<?> returnType) {
        if (returnType == boolean.class) {
            return false;
        }
        if (returnType == int.class) {
            return 0;
        }
        if (returnType == long.class) {
            return 0L;
        }
        return null;
    }
}
