package tech.easyflow.system.service.impl;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.system.enums.CategoryResourceType;
import tech.easyflow.system.enums.ResourceAction;
import tech.easyflow.system.enums.VisibilityScope;
import tech.easyflow.system.permission.resource.VisibilityResource;
import tech.easyflow.system.service.CategoryPermissionService;
import tech.easyflow.system.service.SysDeptService;

import java.lang.reflect.Field;
import java.math.BigInteger;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * {@link ResourceAccessServiceImpl} 资源动作与可见范围回归测试。
 */
public class ResourceAccessServiceImplTest {

    private CategoryPermissionService categoryPermissionService;
    private SysDeptService sysDeptService;
    private ResourceAccessServiceImpl service;

    /**
     * 初始化被测服务及权限依赖。
     *
     * @throws Exception 反射注入失败时抛出
     */
    @Before
    public void setUp() throws Exception {
        categoryPermissionService = Mockito.mock(CategoryPermissionService.class);
        sysDeptService = Mockito.mock(SysDeptService.class);
        service = new ResourceAccessServiceImpl();
        inject(service, "categoryPermissionService", categoryPermissionService);
        inject(service, "sysDeptService", sysDeptService);
    }

    /**
     * 验证创建者始终可以管理自己的 Skill。
     */
    @Test
    public void ownerShouldManageOwnResource() {
        LoginAccount account = account(7, 70);
        VisibilityResource resource = resource(7, 99, 700, VisibilityScope.PRIVATE);

        assertTrue(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.MANAGE));
    }

    /**
     * 验证非创建者即使能查看分类，也不能获得 MANAGE。
     */
    @Test
    public void nonOwnerShouldNotManageResource() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = resource(7, 80, 700, VisibilityScope.PUBLIC);
        Mockito.when(categoryPermissionService.canAccessCategory(
                account, CategoryResourceType.SKILL.getCode(), BigInteger.valueOf(7), BigInteger.valueOf(700)))
                .thenReturn(true);

        assertFalse(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.MANAGE));
    }

    /**
     * 验证 PUBLIC 仍必须先通过分类范围，避免公开标记绕过分类授权。
     */
    @Test
    public void publicResourceShouldStillRequireCategoryAccess() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = resource(7, 90, 700, VisibilityScope.PUBLIC);

        Mockito.when(categoryPermissionService.canAccessCategory(
                account, CategoryResourceType.SKILL.getCode(), BigInteger.valueOf(7), BigInteger.valueOf(700)))
                .thenReturn(false);

        assertFalse(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.READ));
    }

    /**
     * 验证部门可见资源使用现有部门树访问判定。
     */
    @Test
    public void departmentResourceShouldUseDepartmentAccess() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = resource(7, 90, 700, VisibilityScope.DEPT);
        Mockito.when(categoryPermissionService.canAccessCategory(
                account, CategoryResourceType.SKILL.getCode(), BigInteger.valueOf(7), BigInteger.valueOf(700)))
                .thenReturn(true);
        Mockito.when(sysDeptService.canUserAccessDeptScopedResource(BigInteger.valueOf(80), BigInteger.valueOf(90)))
                .thenReturn(true);

        assertTrue(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.READ));
    }

    /**
     * 验证 PRIVATE 对分类内其他用户仍不可见。
     */
    @Test
    public void privateResourceShouldStayPrivateWithinCategory() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = resource(7, 80, 700, VisibilityScope.PRIVATE);
        Mockito.when(categoryPermissionService.canAccessCategory(
                account, CategoryResourceType.SKILL.getCode(), BigInteger.valueOf(7), BigInteger.valueOf(700)))
                .thenReturn(true);

        assertFalse(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.READ));
    }

    /**
     * 验证 Skill 分类 ALL 范围也不能读取其他创建者的未分类私有草稿。
     */
    @Test
    public void allCategoryScopeShouldRejectUnclassifiedPrivateSkill() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = new TestVisibilityResource(
                BigInteger.ONE, BigInteger.valueOf(7), BigInteger.valueOf(90), null,
                VisibilityScope.PRIVATE.name());

        assertFalse(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.READ));
        assertFalse(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.MANAGE));
    }

    /**
     * 验证未分类公开 Agent 不受角色分类白名单限制，但不会影响其它资源类型。
     */
    @Test
    public void unclassifiedPublicAgentShouldBypassCategoryWhitelist() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = new TestVisibilityResource(
                BigInteger.ONE, BigInteger.valueOf(7), BigInteger.valueOf(90), null,
                VisibilityScope.PUBLIC.name());

        assertTrue(service.canAccess(account, CategoryResourceType.AGENT, resource, ResourceAction.USE));
        Mockito.verify(categoryPermissionService, Mockito.never()).canAccessCategory(
                Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any());
    }

    /**
     * 验证未分类 Agent 仍受可见范围约束，私有 Agent 不会因分类放开而被读取。
     */
    @Test
    public void unclassifiedPrivateAgentShouldRemainPrivate() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = new TestVisibilityResource(
                BigInteger.ONE, BigInteger.valueOf(7), BigInteger.valueOf(90), null,
                VisibilityScope.PRIVATE.name());

        assertFalse(service.canAccess(account, CategoryResourceType.AGENT, resource, ResourceAction.READ));
        Mockito.verify(categoryPermissionService, Mockito.never()).canAccessCategory(
                Mockito.any(), Mockito.anyString(), Mockito.any(), Mockito.any());
    }

    /**
     * 验证资源动作不能跨越租户边界，即使资源是公开状态。
     */
    @Test
    public void resourceShouldNeverCrossTenantBoundary() {
        LoginAccount account = account(8, 80);
        VisibilityResource resource = resource(7, 80, 700, VisibilityScope.PUBLIC, BigInteger.TWO);

        assertFalse(service.canAccess(account, CategoryResourceType.SKILL, resource, ResourceAction.READ));
        Mockito.verifyNoInteractions(categoryPermissionService, sysDeptService);
    }

    private LoginAccount account(long id, long deptId) {
        LoginAccount account = new LoginAccount();
        account.setId(BigInteger.valueOf(id));
        account.setDeptId(BigInteger.valueOf(deptId));
        account.setTenantId(BigInteger.ONE);
        return account;
    }

    private VisibilityResource resource(long createdBy, long deptId, long categoryId, VisibilityScope scope) {
        return resource(createdBy, deptId, categoryId, scope, BigInteger.ONE);
    }

    private VisibilityResource resource(long createdBy, long deptId, long categoryId,
                                        VisibilityScope scope, BigInteger tenantId) {
        return new TestVisibilityResource(
                tenantId,
                BigInteger.valueOf(createdBy),
                BigInteger.valueOf(deptId),
                BigInteger.valueOf(categoryId),
                scope.name());
    }

    private void inject(Object target, String fieldName, Object value) throws Exception {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    private record TestVisibilityResource(BigInteger tenantId,
                                          BigInteger createdBy,
                                          BigInteger deptId,
                                          BigInteger categoryId,
                                          String visibilityScope) implements VisibilityResource {
        @Override
        public BigInteger getTenantId() {
            return tenantId;
        }

        @Override
        public BigInteger getCreatedBy() {
            return createdBy;
        }

        @Override
        public BigInteger getDeptId() {
            return deptId;
        }

        @Override
        public BigInteger getCategoryId() {
            return categoryId;
        }

        @Override
        public String getVisibilityScope() {
            return visibilityScope;
        }
    }
}
