package tech.easyflow.agent.runtime.hitl;

import com.easyagents.agent.runtime.event.AgentRuntimeEvent;
import com.easyagents.agent.runtime.event.AgentRuntimeEventType;
import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import tech.easyflow.agent.config.AgentRuntimeProperties;
import tech.easyflow.agent.entity.AgentHitlPending;
import tech.easyflow.agent.mapper.AgentHitlPendingMapper;

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

/**
 * {@link AgentHitlPendingServiceImpl} 回归测试。
 */
public class AgentHitlPendingServiceImplTest {

    /**
     * 验证审批 pending 仅持久化可审查的脱敏工具参数与元数据。
     */
    @Test
    public void recordApprovalRequiredShouldRedactSensitiveInputAndMetadata() {
        AgentHitlPendingMapper mapper = Mockito.mock(AgentHitlPendingMapper.class);
        AgentHitlPendingServiceImpl service =
                new AgentHitlPendingServiceImpl(mapper, new AgentRuntimeProperties());
        AgentRuntimeEvent event = AgentRuntimeEvent.of(AgentRuntimeEventType.TOOL_APPROVAL_REQUIRED);
        event.getPayload().put("resumeToken", "resume-1");
        event.getPayload().put("toolName", "search");
        event.getPayload().put("toolInput", Map.of(
                "authorization", "sentinel-secret-authorization",
                "keyword", "EasyFlow",
                "nested", Map.of("password", "sentinel-secret-password")));
        event.getPayload().put("approvalMetadata", Map.of(
                "credential", "sentinel-secret-credential",
                "risk", "low"));

        service.recordApprovalRequired("request-1", null, event);

        ArgumentCaptor<AgentHitlPending> captor = ArgumentCaptor.forClass(AgentHitlPending.class);
        Mockito.verify(mapper).insertOrUpdate(captor.capture());
        AgentHitlPending stored = captor.getValue();
        Assert.assertEquals("EasyFlow", stored.getToolInputJson().get("keyword"));
        Assert.assertEquals("[已隐藏]", stored.getToolInputJson().get("authorization"));
        Assert.assertFalse(stored.getToolInputJson().toString().contains("sentinel-secret"));
        Assert.assertEquals("low", stored.getMetadataJson().get("risk"));
        Assert.assertFalse(stored.getMetadataJson().toString().contains("sentinel-secret"));
    }

    /**
     * 验证过期扫描只返回成功从 PENDING 原子更新为 EXPIRED 的记录。
     */
    @Test
    public void expirePendingShouldIgnoreConcurrentlyConsumedRecord() {
        AgentHitlPendingMapper mapper = Mockito.mock(AgentHitlPendingMapper.class);
        AgentHitlPending first = pending(BigInteger.ONE, "request-1", "token-1");
        AgentHitlPending concurrentlyApproved = pending(BigInteger.TWO, "request-2", "token-2");
        Mockito.when(mapper.selectListByQuery(Mockito.any(QueryWrapper.class)))
                .thenReturn(List.of(first, concurrentlyApproved));
        Mockito.when(mapper.updateByQuery(
                        Mockito.any(AgentHitlPending.class), Mockito.any(QueryWrapper.class)))
                .thenReturn(1, 0);
        AgentHitlPendingServiceImpl service =
                new AgentHitlPendingServiceImpl(mapper, new AgentRuntimeProperties());

        List<AgentHitlPending> expired = service.expirePending(100);

        Assert.assertEquals(1, expired.size());
        Assert.assertSame(first, expired.get(0));
        Assert.assertEquals(AgentHitlPendingStatus.EXPIRED.name(), first.getStatus());
        Assert.assertEquals(AgentHitlPendingStatus.PENDING.name(), concurrentlyApproved.getStatus());
        Mockito.verify(mapper, Mockito.times(2)).updateByQuery(
                Mockito.any(AgentHitlPending.class), Mockito.any(QueryWrapper.class));
    }

    /**
     * 创建待过期审批记录。
     *
     * @param id 记录 ID
     * @param requestId 请求 ID
     * @param resumeToken 恢复令牌
     * @return 审批记录
     */
    private AgentHitlPending pending(BigInteger id, String requestId, String resumeToken) {
        AgentHitlPending pending = new AgentHitlPending();
        pending.setId(id);
        pending.setRequestId(requestId);
        pending.setResumeToken(resumeToken);
        pending.setStatus(AgentHitlPendingStatus.PENDING.name());
        pending.setIsDeleted(0);
        return pending;
    }
}
