package tech.easyflow.agent.runtime.artifact;

import org.junit.Assert;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import tech.easyflow.agent.entity.AgentArtifact;
import tech.easyflow.agent.mapper.AgentArtifactMapper;

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

/**
 * {@link AgentArtifactCleanupScheduler} 超时发布恢复测试。
 */
public class AgentArtifactCleanupSchedulerTest {

    /**
     * 验证过期 PUBLISHING 记录经条件领取后立即进入幂等对象删除。
     */
    @Test
    public void shouldClaimAndDeleteAbandonedPublishingArtifact() {
        AgentArtifactMapper mapper = Mockito.mock(AgentArtifactMapper.class);
        AgentArtifactService artifactService = Mockito.mock(AgentArtifactService.class);
        AgentArtifact abandoned = publishingArtifact();
        Mockito.when(mapper.selectListByQuery(Mockito.any()))
                .thenReturn(List.of(), List.of(abandoned), List.of(), List.of());
        Mockito.when(mapper.selectOrphanedFormalArtifacts(Mockito.anyInt())).thenReturn(List.of());
        Mockito.when(mapper.updateByQuery(Mockito.any(), Mockito.any())).thenReturn(1);

        new AgentArtifactCleanupScheduler(mapper, artifactService).cleanup();

        ArgumentCaptor<AgentArtifact> update = ArgumentCaptor.forClass(AgentArtifact.class);
        Mockito.verify(mapper).updateByQuery(update.capture(), Mockito.any());
        Assert.assertEquals(AgentArtifactStatus.DELETE_PENDING.name(), update.getValue().getStatus());
        Assert.assertEquals("ARTIFACT_PUBLISH_TIMEOUT", update.getValue().getLastErrorCode());
        Assert.assertNotNull(update.getValue().getNextRetryAt());
        Mockito.verify(artifactService).deleteObject(abandoned);
    }

    /**
     * 验证条件领取失败时不会删除可能已被其他线程接管的对象。
     */
    @Test
    public void shouldSkipDeleteWhenPublishingClaimLosesRace() {
        AgentArtifactMapper mapper = Mockito.mock(AgentArtifactMapper.class);
        AgentArtifactService artifactService = Mockito.mock(AgentArtifactService.class);
        Mockito.when(mapper.selectListByQuery(Mockito.any()))
                .thenReturn(List.of(), List.of(publishingArtifact()), List.of(), List.of());
        Mockito.when(mapper.selectOrphanedFormalArtifacts(Mockito.anyInt())).thenReturn(List.of());
        Mockito.when(mapper.updateByQuery(Mockito.any(), Mockito.any())).thenReturn(0);

        new AgentArtifactCleanupScheduler(mapper, artifactService).cleanup();

        Mockito.verify(artifactService, Mockito.never()).deleteObject(Mockito.any());
    }

    /**
     * 验证会话删除已落库但 after hook 失败时可由孤儿扫描补偿删除。
     */
    @Test
    public void shouldClaimAndDeleteFormalArtifactWhoseSessionIsUnavailable() {
        AgentArtifactMapper mapper = Mockito.mock(AgentArtifactMapper.class);
        AgentArtifactService artifactService = Mockito.mock(AgentArtifactService.class);
        AgentArtifact orphan = publishingArtifact();
        orphan.setStatus(AgentArtifactStatus.AVAILABLE.name());
        Mockito.when(mapper.selectListByQuery(Mockito.any()))
                .thenReturn(List.of(), List.of(), List.of(), List.of());
        Mockito.when(mapper.selectOrphanedFormalArtifacts(Mockito.anyInt())).thenReturn(List.of(orphan));
        Mockito.when(mapper.updateByQuery(Mockito.any(), Mockito.any())).thenReturn(1);

        new AgentArtifactCleanupScheduler(mapper, artifactService).cleanup();

        ArgumentCaptor<AgentArtifact> update = ArgumentCaptor.forClass(AgentArtifact.class);
        Mockito.verify(mapper).updateByQuery(update.capture(), Mockito.any());
        Assert.assertEquals(AgentArtifactStatus.DELETE_PENDING.name(), update.getValue().getStatus());
        Assert.assertEquals("ARTIFACT_SESSION_UNAVAILABLE", update.getValue().getLastErrorCode());
        Mockito.verify(artifactService).deleteObject(orphan);
    }

    private AgentArtifact publishingArtifact() {
        AgentArtifact artifact = new AgentArtifact();
        artifact.setId(BigInteger.valueOf(9));
        artifact.setArtifactId("artifact-9");
        artifact.setObjectKey("artifacts/9/content");
        artifact.setStatus(AgentArtifactStatus.PUBLISHING.name());
        artifact.setNextRetryAt(new Date(System.currentTimeMillis() - 1_000));
        return artifact;
    }
}
