package tech.easyflow.skill.service.impl;

import com.mybatisflex.core.query.QueryWrapper;
import org.junit.After;
import org.junit.Before;
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 java.math.BigInteger;
import java.util.List;

import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.spy;

/**
 * {@link SkillCategoryServiceImpl} 分类循环、深度和删除约束测试。
 */
public class SkillCategoryServiceImplTest {

    private SkillCategoryServiceImpl service;
    private MockedStatic<SaTokenUtil> saToken;

    /**
     * 初始化可隔离父级查询的分类服务。
     */
    @Before
    public void setUp() {
        service = spy(new SkillCategoryServiceImpl());
        LoginAccount account = new LoginAccount();
        account.setId(BigInteger.ONE);
        account.setTenantId(BigInteger.ONE);
        saToken = mockStatic(SaTokenUtil.class);
        saToken.when(SaTokenUtil::getLoginAccount).thenReturn(account);
    }

    /**
     * 释放静态登录态 Mock。
     */
    @After
    public void tearDown() {
        saToken.close();
    }

    /**
     * 验证分类不能将自身设置为父级。
     */
    @Test
    public void categoryCannotBeItsOwnParent() {
        SkillCategory category = category(1, 1, 1, "");
        doReturn(category).when(service).getById(BigInteger.ONE);

        assertThrows(BusinessException.class, () -> service.updateById(category));
    }

    /**
     * 验证创建或移动到三级父级下会因形成第四级而被拒绝。
     */
    @Test
    public void categoryCannotMoveBelowLevelThreeParent() {
        SkillCategory category = category(10, 3, 1, "");
        SkillCategory parent = category(3, null, 3, "1,2");
        doReturn(parent).when(service).getById(BigInteger.valueOf(3));

        assertThrows(BusinessException.class, () -> service.updateById(category));
    }

    /**
     * 验证分类不能移动到自己的直接或间接后代下。
     */
    @Test
    public void categoryCannotMoveUnderDescendant() {
        SkillCategory category = category(1, 2, 1, "");
        SkillCategory descendant = category(2, 1, 2, "1");
        doReturn(descendant).when(service).getById(BigInteger.valueOf(2));

        assertThrows(BusinessException.class, () -> service.updateById(category));
    }

    /**
     * 验证移动带子树分类时需要按整个子树的新深度执行三级限制。
     */
    @Test
    public void movingSubtreeCannotPushDescendantBeyondLevelThree() {
        SkillCategory category = category(1, 9, 1, "");
        SkillCategory newParent = category(9, null, 1, "");
        SkillCategory child = category(2, 1, 2, "1");
        SkillCategory grandchild = category(3, 2, 3, "1,2");
        doReturn(newParent).when(service).getById(BigInteger.valueOf(9));
        doReturn(List.of(child, grandchild)).when(service).list(any(QueryWrapper.class));

        assertThrows(BusinessException.class, () -> service.updateById(category));
    }

    /**
     * 验证存在子分类时删除约束由服务层统一执行。
     */
    @Test
    public void categoryWithChildrenCannotBeDeletedAtServiceLayer() {
        doReturn(category(1, null, 1, "")).when(service).getById(BigInteger.ONE);
        doReturn(true).when(service).hasChildren(BigInteger.ONE);

        assertThrows(BusinessException.class, () -> service.removeById(BigInteger.ONE));
    }

    /**
     * 创建分类测试数据。
     *
     * @param id 分类 ID
     * @param parentId 父级 ID
     * @param level 层级
     * @param ancestors 祖先路径
     * @return 分类
     */
    private SkillCategory category(long id, Integer parentId, int level, String ancestors) {
        SkillCategory category = new SkillCategory();
        category.setId(BigInteger.valueOf(id));
        category.setParentId(parentId == null ? null : BigInteger.valueOf(parentId));
        category.setCategoryName("category-" + id);
        category.setLevelNo(level);
        category.setAncestors(ancestors);
        category.setStatus(1);
        category.setTenantId(BigInteger.ONE);
        return category;
    }
}
