package tech.easyflow.skill.service.impl;

import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.mockito.MockedStatic;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.SkillCategory;
import tech.easyflow.skill.mapper.SkillCategoryMapper;

import java.math.BigInteger;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Skill 分类租户边界与循环约束的有效路径测试。
 */
public class SkillCategoryTenantConstraintTest {

    /**
     * 验证同租户且登录态有效时，自身父级循环仍会被业务规则拒绝。
     */
    @Test
    public void selfParentShouldBeRejectedWithValidTenantContext() {
        SkillCategoryServiceImpl service = spy(new SkillCategoryServiceImpl());
        SkillCategoryMapper mapper = mock(SkillCategoryMapper.class);
        doReturn(mapper).when(service).getMapper();
        SkillCategory category = category(1, 1, 1);
        when(mapper.selectTenantTreeForUpdate(BigInteger.ONE)).thenReturn(List.of(category));

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account(7, 1));
            BusinessException exception = assertThrows(
                    BusinessException.class, () -> service.updateById(category));
            assertEquals("父级分类不能是自身", exception.getMessage());
        }
    }

    /**
     * 验证等待并发事务锁后使用最新分类树重新检查循环关系。
     */
    @Test
    public void concurrentMoveShouldUseLockedLatestTreeAndRejectCycle() {
        SkillCategoryServiceImpl service = spy(new SkillCategoryServiceImpl());
        SkillCategoryMapper mapper = mock(SkillCategoryMapper.class);
        doReturn(mapper).when(service).getMapper();

        SkillCategory movedA = category(1, 2, 1);
        movedA.setLevelNo(2);
        movedA.setAncestors("2");
        SkillCategory rootB = category(2, null, 1);
        rootB.setAncestors("");
        when(mapper.selectTenantTreeForUpdate(BigInteger.ONE)).thenReturn(List.of(movedA, rootB));

        SkillCategory moveBUnderA = category(2, 1, 1);
        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account(7, 1));
            BusinessException exception = assertThrows(
                    BusinessException.class, () -> service.updateById(moveBUnderA));
            assertEquals("父级分类不能是当前分类的后代", exception.getMessage());
        }
    }

    /**
     * 验证其他租户的分类不能被当前租户用作 Skill 分类。
     */
    @Test
    public void categoryFromAnotherTenantShouldBeTreatedAsMissing() {
        SkillCategoryServiceImpl service = spy(new SkillCategoryServiceImpl());
        doReturn(mock(SkillCategoryMapper.class)).when(service).getMapper();
        doReturn(null).when(service).getOne(any(QueryWrapper.class));

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account(7, 1));
            BusinessException exception = assertThrows(BusinessException.class,
                    () -> service.validateUsableCategory(BigInteger.valueOf(9)));
            assertEquals("Skill 分类不存在", exception.getMessage());
        }
    }

    /**
     * Skill 归类写入必须复用分类结构变更的完整租户树排他锁。
     */
    @Test
    public void skillCategoryAssignmentShouldLockTenantTree() {
        SkillCategoryServiceImpl service = spy(new SkillCategoryServiceImpl());
        SkillCategoryMapper mapper = mock(SkillCategoryMapper.class);
        doReturn(mapper).when(service).getMapper();
        SkillCategory target = category(9, null, 1);
        when(mapper.selectTenantTreeForUpdate(BigInteger.ONE)).thenReturn(List.of(target));

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account(7, 1));
            service.lockAndValidateUsableCategory(target.getId());
        }

        verify(mapper).selectTenantTreeForUpdate(BigInteger.ONE);
    }

    /**
     * 移出分类同样必须锁树，确保分类删除在移动提交后重新检查占用。
     */
    @Test
    public void movingSkillToUncategorizedShouldStillLockTenantTree() {
        SkillCategoryServiceImpl service = spy(new SkillCategoryServiceImpl());
        SkillCategoryMapper mapper = mock(SkillCategoryMapper.class);
        doReturn(mapper).when(service).getMapper();
        when(mapper.selectTenantTreeForUpdate(BigInteger.ONE)).thenReturn(List.of());

        try (MockedStatic<SaTokenUtil> saToken = mockStatic(SaTokenUtil.class)) {
            saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account(7, 1));
            service.lockAndValidateUsableCategory(null);
        }

        verify(mapper).selectTenantTreeForUpdate(BigInteger.ONE);
    }

    private SkillCategory category(long id, Integer parentId, long tenantId) {
        SkillCategory category = new SkillCategory();
        category.setId(BigInteger.valueOf(id));
        category.setTenantId(BigInteger.valueOf(tenantId));
        category.setParentId(parentId == null ? null : BigInteger.valueOf(parentId));
        category.setCategoryName("category-" + id);
        category.setLevelNo(1);
        category.setStatus(1);
        return category;
    }

    private LoginAccount account(long accountId, long tenantId) {
        LoginAccount account = new LoginAccount();
        account.setId(BigInteger.valueOf(accountId));
        account.setTenantId(BigInteger.valueOf(tenantId));
        return account;
    }
}
