package tech.easyflow.agent.service.impl;

import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.entity.AgentToolBinding;
import tech.easyflow.agent.enums.AgentToolType;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.agent.support.AgentBindingLockExecutor;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.function.Supplier;

/**
 * {@link AgentResourceBindingProviderImpl} 批量解绑锁顺序测试。
 */
public class AgentResourceBindingProviderImplTest {

    /**
     * 验证工作流批量解绑按 Agent ID 升序获取绑定锁。
     */
    @Test
    public void unbindWorkflowShouldAcquireAgentLocksInAscendingOrder() {
        AgentService agentService = Mockito.mock(AgentService.class);
        AgentToolBindingService toolBindingService = Mockito.mock(AgentToolBindingService.class);
        AgentKnowledgeBindingService knowledgeBindingService =
                Mockito.mock(AgentKnowledgeBindingService.class);
        AgentBindingLockExecutor lockExecutor = Mockito.mock(AgentBindingLockExecutor.class);
        Mockito.when(toolBindingService.list(Mockito.any(QueryWrapper.class)))
                .thenReturn(List.of(
                        workflowBinding(3),
                        workflowBinding(1),
                        workflowBinding(2),
                        workflowBinding(1)
                ));
        List<BigInteger> lockOrder = new ArrayList<>();
        Mockito.doAnswer(invocation -> {
            lockOrder.add(invocation.getArgument(0));
            Supplier<?> task = invocation.getArgument(1);
            return task.get();
        }).when(lockExecutor).execute(Mockito.any(BigInteger.class), Mockito.any());
        AgentResourceBindingProviderImpl provider = new AgentResourceBindingProviderImpl(
                agentService,
                toolBindingService,
                knowledgeBindingService,
                lockExecutor
        );

        provider.unbindWorkflow(BigInteger.TEN);

        Assert.assertEquals(
                List.of(BigInteger.ONE, BigInteger.TWO, BigInteger.valueOf(3)),
                lockOrder
        );
    }

    /**
     * 已发布快照中的引用必须参与资源删除影响检查。
     */
    @Test
    public void listAgentsByWorkflowIdShouldIncludeSnapshotOnlyReference() {
        AgentService agentService = Mockito.mock(AgentService.class);
        AgentToolBindingService toolBindingService = Mockito.mock(AgentToolBindingService.class);
        AgentKnowledgeBindingService knowledgeBindingService =
                Mockito.mock(AgentKnowledgeBindingService.class);
        AgentBindingLockExecutor lockExecutor = Mockito.mock(AgentBindingLockExecutor.class);
        BigInteger agentId = BigInteger.valueOf(7);
        Agent agent = new Agent();
        agent.setId(agentId);
        agent.setName("已发布智能体");
        agent.setPublishedSnapshotJson(Map.of(
                "toolBindings",
                List.of(Map.of(
                        "toolType", AgentToolType.WORKFLOW.name(),
                        "targetId", BigInteger.TEN
                ))
        ));
        Mockito.when(toolBindingService.list(Mockito.any(QueryWrapper.class)))
                .thenReturn(List.of());
        Mockito.when(agentService.list(Mockito.any(QueryWrapper.class)))
                .thenReturn(List.of(agent));
        Mockito.when(agentService.listByIds(Mockito.anyCollection()))
                .thenReturn(List.of(agent));
        AgentResourceBindingProviderImpl provider = new AgentResourceBindingProviderImpl(
                agentService,
                toolBindingService,
                knowledgeBindingService,
                lockExecutor
        );

        var result = provider.listAgentsByWorkflowId(BigInteger.TEN);

        Assert.assertEquals(1, result.size());
        Assert.assertEquals(agentId, result.get(0).getId());
        Assert.assertEquals("已发布智能体", result.get(0).getTitle());
    }

    /**
     * Agent 已发布 Skill 内部的 Tool 引用也必须阻止资源下线或删除。
     */
    @Test
    public void listAgentsByWorkflowIdShouldIncludeNestedSkillToolReference() {
        AgentService agentService = Mockito.mock(AgentService.class);
        AgentToolBindingService toolBindingService = Mockito.mock(AgentToolBindingService.class);
        AgentKnowledgeBindingService knowledgeBindingService =
                Mockito.mock(AgentKnowledgeBindingService.class);
        BigInteger agentId = BigInteger.valueOf(8);
        Agent agent = new Agent();
        agent.setId(agentId);
        agent.setName("Skill 智能体");
        agent.setPublishedSnapshotJson(Map.of(
                "skillBindings", List.of(Map.of(
                        "skillId", BigInteger.ONE,
                        "resourceSnapshot", Map.of(
                                "toolBindings", List.of(Map.of(
                                        "toolType", AgentToolType.WORKFLOW.name(),
                                        "targetId", BigInteger.TEN)))))));
        Mockito.when(toolBindingService.list(Mockito.any(QueryWrapper.class))).thenReturn(List.of());
        Mockito.when(agentService.list(Mockito.any(QueryWrapper.class))).thenReturn(List.of(agent));
        Mockito.when(agentService.listByIds(Mockito.anyCollection())).thenReturn(List.of(agent));
        AgentResourceBindingProviderImpl provider = new AgentResourceBindingProviderImpl(
                agentService,
                toolBindingService,
                knowledgeBindingService,
                Mockito.mock(AgentBindingLockExecutor.class));

        var result = provider.listAgentsByWorkflowId(BigInteger.TEN);

        Assert.assertEquals(1, result.size());
        Assert.assertEquals(agentId, result.get(0).getId());
    }

    /**
     * 构造工作流工具绑定。
     *
     * @param agentId Agent ID
     * @return 工具绑定
     */
    private static AgentToolBinding workflowBinding(long agentId) {
        AgentToolBinding binding = new AgentToolBinding();
        binding.setAgentId(BigInteger.valueOf(agentId));
        binding.setToolType(AgentToolType.WORKFLOW.name());
        binding.setTargetId(BigInteger.TEN);
        return binding;
    }
}
