package tech.easyflow.agent.publish;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.core.util.LambdaGetter;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.agent.entity.Agent;
import tech.easyflow.agent.service.AgentKnowledgeBindingService;
import tech.easyflow.agent.service.AgentService;
import tech.easyflow.agent.service.AgentToolBindingService;
import tech.easyflow.ai.enums.PublishStatus;

import java.lang.reflect.Proxy;
import java.math.BigInteger;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * {@link AgentApprovalSubjectHandler} 单元测试。
 */
public class AgentApprovalSubjectHandlerTest {

    /**
     * 审批状态变更必须使用字段级更新，避免覆盖 Agent 草稿配置。
     */
    @Test
    public void persistResourceStateShouldUseSelectiveUpdate() {
        AgentService agentService = mock(AgentService.class);
        UpdateChain<Agent> updateChain = prepareUpdateChain(agentService);
        AgentApprovalSubjectHandler handler = handler(agentService);

        handler.persistResourceState(
                BigInteger.valueOf(1001),
                PublishStatus.PUBLISH_PENDING,
                BigInteger.valueOf(2001)
        );

        verifySelectiveUpdate(updateChain, BigInteger.valueOf(1001), 2);
        verify(agentService, never()).updateById(any(Agent.class));
    }

    /**
     * 发布 Agent 必须使用字段级更新，确保发布后草稿配置仍可回显。
     */
    @Test
    public void publishResourceShouldUseSelectiveUpdate() {
        AgentService agentService = mock(AgentService.class);
        UpdateChain<Agent> updateChain = prepareUpdateChain(agentService);
        AgentApprovalSubjectHandler handler = handler(agentService);
        Map<String, Object> snapshot = new LinkedHashMap<>();
        snapshot.put("interactionConfigJson", Map.of("welcomeMessage", "你好"));

        handler.publishResource(BigInteger.valueOf(1001), snapshot, BigInteger.valueOf(3001));

        verifySelectiveUpdate(updateChain, BigInteger.valueOf(1001), 5);
        verify(agentService, never()).updateById(any(Agent.class));
    }

    /**
     * Agent 下线必须使用字段级更新，避免覆盖未发布配置。
     */
    @Test
    public void markResourceOfflineShouldUseSelectiveUpdate() {
        AgentService agentService = mock(AgentService.class);
        UpdateChain<Agent> updateChain = prepareUpdateChain(agentService);
        AgentApprovalSubjectHandler handler = handler(agentService);

        handler.markResourceOffline(BigInteger.valueOf(1001));

        verifySelectiveUpdate(updateChain, BigInteger.valueOf(1001), 2);
        verify(agentService, never()).updateById(any(Agent.class));
    }

    /**
     * 审批删除 Agent 前必须同步清理工具绑定和知识库绑定，避免留下孤儿数据。
     */
    @Test
    public void beforeRemoveShouldCleanAgentBindings() {
        AtomicInteger toolRemoveCalls = new AtomicInteger();
        AtomicInteger knowledgeRemoveCalls = new AtomicInteger();
        AgentToolBindingService toolBindingService = proxy(AgentToolBindingService.class, toolRemoveCalls);
        AgentKnowledgeBindingService knowledgeBindingService = proxy(AgentKnowledgeBindingService.class, knowledgeRemoveCalls);
        AgentApprovalSubjectHandler handler = new AgentApprovalSubjectHandler(
                null,
                new ObjectMapper(),
                null,
                toolBindingService,
                knowledgeBindingService,
                null
        );

        handler.beforeRemove(BigInteger.valueOf(1001));

        Assert.assertEquals(1, toolRemoveCalls.get());
        Assert.assertEquals(1, knowledgeRemoveCalls.get());
    }

    /**
     * 创建用于生命周期测试的处理器。
     *
     * @param agentService Agent 服务
     * @return Agent 审批资源处理器
     */
    private static AgentApprovalSubjectHandler handler(AgentService agentService) {
        return new AgentApprovalSubjectHandler(
                null,
                new ObjectMapper(),
                agentService,
                null,
                null,
                null
        );
    }

    /**
     * 准备字段更新对象。
     *
     * @param agentService Agent 服务
     * @return 字段更新对象
     */
    @SuppressWarnings("unchecked")
    private static UpdateChain<Agent> prepareUpdateChain(AgentService agentService) {
        UpdateChain<Agent> updateChain = mock(UpdateChain.class);
        when(agentService.updateChain()).thenReturn(updateChain);
        return updateChain;
    }

    /**
     * 校验生命周期操作仅通过指定主键执行字段更新。
     *
     * @param updateChain 字段更新对象
     * @param resourceId Agent 主键
     * @param fieldCount 更新字段数量
     */
    @SuppressWarnings("unchecked")
    private static void verifySelectiveUpdate(UpdateChain<Agent> updateChain,
                                              BigInteger resourceId,
                                              int fieldCount) {
        verify(updateChain, times(fieldCount)).set(any(LambdaGetter.class), any());
        verify(updateChain).eq(any(LambdaGetter.class), eq(resourceId));
        verify(updateChain).update();
    }

    /**
     * 创建只统计删除调用的服务代理。
     *
     * @param type 服务接口类型
     * @param removeCalls 删除调用计数器
     * @param <T> 服务接口类型
     * @return 服务代理
     */
    @SuppressWarnings("unchecked")
    private static <T> T proxy(Class<T> type, AtomicInteger removeCalls) {
        return (T) Proxy.newProxyInstance(
                type.getClassLoader(),
                new Class<?>[]{type},
                (proxy, method, args) -> {
                    if ("remove".equals(method.getName()) && args != null && args.length == 1) {
                        removeCalls.incrementAndGet();
                        return true;
                    }
                    if (method.getReturnType() == boolean.class || method.getReturnType() == Boolean.class) {
                        return false;
                    }
                    return null;
                }
        );
    }
}
