package tech.easyflow.ai.documentimport.task;

import com.mybatisflex.core.query.QueryWrapper;
import org.junit.Test;
import org.mockito.Mockito;
import tech.easyflow.ai.entity.DocumentImportSnapshotCleanup;
import tech.easyflow.ai.mapper.DocumentImportSnapshotCleanupMapper;
import tech.easyflow.ai.mapper.DocumentMapper;

import java.lang.reflect.Field;
import java.math.BigInteger;
import java.util.Date;
import java.util.concurrent.atomic.AtomicReference;

/**
 * {@link DocumentImportSnapshotCleanupService} 可靠清理测试。
 */
public class DocumentImportSnapshotCleanupServiceTest {

    /**
     * 验证分片和清单按持久化阶段顺序删除。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void shouldPersistPhaseBeforeDeletingManifest()
        throws Exception {
        CleanupHarness harness = createHarness();

        harness.service.scheduleChunkSnapshot(
            "snapshot/chunk-manifest.json");

        Mockito.verify(harness.chunkSnapshotService).deleteParts(
            "snapshot/chunk-manifest.json");
        Mockito.verify(harness.cleanupMapper).advanceToManifest(
            Mockito.any(BigInteger.class),
            Mockito.anyString(),
            Mockito.any(Date.class));
        Mockito.verify(harness.chunkSnapshotService).deleteManifest(
            "snapshot/chunk-manifest.json");
        Mockito.verify(harness.cleanupMapper).deleteCompleted(
            Mockito.any(BigInteger.class),
            Mockito.anyString());
        Mockito.verify(harness.cleanupMapper, Mockito.never())
            .releaseForRetry(
                Mockito.any(BigInteger.class),
                Mockito.anyString(),
                Mockito.any(Date.class),
                Mockito.anyString(),
                Mockito.any(Date.class));
    }

    /**
     * 验证分片删除失败后保留记录并安排重试。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void shouldReleaseFailedCleanupForRetry()
        throws Exception {
        CleanupHarness harness = createHarness();
        Mockito.doThrow(new IllegalStateException("对象存储暂不可用"))
            .when(harness.chunkSnapshotService)
            .deleteParts("snapshot/failing-manifest.json");

        harness.service.scheduleChunkSnapshot(
            "snapshot/failing-manifest.json");

        Mockito.verify(harness.cleanupMapper).releaseForRetry(
            Mockito.any(BigInteger.class),
            Mockito.anyString(),
            Mockito.any(Date.class),
            Mockito.contains("对象存储暂不可用"),
            Mockito.any(Date.class));
        Mockito.verify(harness.chunkSnapshotService, Mockito.never())
            .deleteManifest(Mockito.anyString());
        Mockito.verify(harness.cleanupMapper, Mockito.never())
            .deleteCompleted(
                Mockito.any(BigInteger.class),
                Mockito.anyString());
    }

    /**
     * 验证 CSV 清单删除后原子清理文档中的旧快照指针。
     *
     * @throws Exception 反射注入失败
     */
    @Test
    public void shouldClearCsvPointerAfterManifestDeletion()
        throws Exception {
        CleanupHarness harness = createHarness();
        BigInteger knowledgeId = BigInteger.valueOf(7);
        BigInteger documentId = BigInteger.valueOf(9);

        harness.service.scheduleCsvTableSnapshot(
            knowledgeId,
            documentId,
            "snapshot/csv-manifest.json");

        Mockito.verify(harness.csvTableSnapshotService).deleteParts(
            "snapshot/csv-manifest.json");
        Mockito.verify(harness.csvTableSnapshotService).deleteManifest(
            "snapshot/csv-manifest.json");
        Mockito.verify(harness.documentMapper).clearCsvSnapshotPath(
            Mockito.eq(documentId),
            Mockito.eq("snapshot/csv-manifest.json"),
            Mockito.any(Date.class));
    }

    /**
     * 创建带内存状态的清理服务测试夹具。
     *
     * @return 测试夹具
     * @throws Exception 反射注入失败
     */
    @SuppressWarnings("unchecked")
    private CleanupHarness createHarness() throws Exception {
        DocumentImportSnapshotCleanupService service =
            new DocumentImportSnapshotCleanupService();
        DocumentImportSnapshotCleanupMapper cleanupMapper =
            Mockito.mock(DocumentImportSnapshotCleanupMapper.class);
        DocumentImportChunkSnapshotService chunkSnapshotService =
            Mockito.mock(DocumentImportChunkSnapshotService.class);
        CsvTableSnapshotService csvTableSnapshotService =
            Mockito.mock(CsvTableSnapshotService.class);
        DocumentMapper documentMapper =
            Mockito.mock(DocumentMapper.class);
        AtomicReference<DocumentImportSnapshotCleanup> recordRef =
            new AtomicReference<DocumentImportSnapshotCleanup>();

        Mockito.when(cleanupMapper.insertIgnore(
            Mockito.any(DocumentImportSnapshotCleanup.class)))
            .thenAnswer(invocation -> {
                recordRef.set(invocation.getArgument(0));
                return 1;
            });
        Mockito.when(cleanupMapper.selectOneByQuery(
            Mockito.any(QueryWrapper.class)))
            .thenAnswer(invocation -> recordRef.get());
        Mockito.when(cleanupMapper.claim(
            Mockito.any(BigInteger.class),
            Mockito.anyString(),
            Mockito.any(Date.class),
            Mockito.any(Date.class)))
            .thenAnswer(invocation -> {
                DocumentImportSnapshotCleanup record = recordRef.get();
                record.setExecutionToken(invocation.getArgument(1));
                record.setAttemptCount(
                    (record.getAttemptCount() == null
                        ? 0
                        : record.getAttemptCount()) + 1);
                return 1;
            });
        Mockito.when(cleanupMapper.advanceToManifest(
            Mockito.any(BigInteger.class),
            Mockito.anyString(),
            Mockito.any(Date.class)))
            .thenAnswer(invocation -> {
                recordRef.get().setPhase("MANIFEST_PENDING");
                return 1;
            });
        Mockito.when(cleanupMapper.deleteCompleted(
            Mockito.any(BigInteger.class),
            Mockito.anyString())).thenReturn(1);
        Mockito.when(cleanupMapper.releaseForRetry(
            Mockito.any(BigInteger.class),
            Mockito.anyString(),
            Mockito.any(Date.class),
            Mockito.anyString(),
            Mockito.any(Date.class))).thenReturn(1);

        setField(service, "cleanupMapper", cleanupMapper);
        setField(
            service, "chunkSnapshotService", chunkSnapshotService);
        setField(
            service, "csvTableSnapshotService", csvTableSnapshotService);
        setField(service, "documentMapper", documentMapper);
        return new CleanupHarness(
            service,
            cleanupMapper,
            chunkSnapshotService,
            csvTableSnapshotService,
            documentMapper);
    }

    /**
     * 反射设置测试字段。
     *
     * @param target 目标对象
     * @param fieldName 字段名
     * @param value 字段值
     * @throws Exception 字段不存在或不可访问
     */
    private void setField(
        Object target,
        String fieldName,
        Object value) throws Exception {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    /**
     * 快照清理测试依赖集合。
     */
    private static final class CleanupHarness {

        private final DocumentImportSnapshotCleanupService service;
        private final DocumentImportSnapshotCleanupMapper cleanupMapper;
        private final DocumentImportChunkSnapshotService
            chunkSnapshotService;
        private final CsvTableSnapshotService csvTableSnapshotService;
        private final DocumentMapper documentMapper;

        /**
         * 创建测试依赖集合。
         *
         * @param service 清理服务
         * @param cleanupMapper 清理 Mapper
         * @param chunkSnapshotService 分块快照服务
         * @param csvTableSnapshotService CSV 快照服务
         * @param documentMapper 文档 Mapper
         */
        private CleanupHarness(
            DocumentImportSnapshotCleanupService service,
            DocumentImportSnapshotCleanupMapper cleanupMapper,
            DocumentImportChunkSnapshotService chunkSnapshotService,
            CsvTableSnapshotService csvTableSnapshotService,
            DocumentMapper documentMapper) {
            this.service = service;
            this.cleanupMapper = cleanupMapper;
            this.chunkSnapshotService = chunkSnapshotService;
            this.csvTableSnapshotService = csvTableSnapshotService;
            this.documentMapper = documentMapper;
        }
    }
}
