package tech.easyflow.ai.service.impl;

import com.easyagents.core.document.Document;
import com.easyagents.search.engine.service.DocumentSearcher;
import com.easyagents.search.engine.service.KeywordSearchRequest;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.ObjectProvider;
import tech.easyflow.ai.config.SearcherFactory;
import tech.easyflow.ai.enums.DocumentProcessStatus;
import tech.easyflow.ai.mapper.DocumentChunkMapper;
import tech.easyflow.ai.mapper.DocumentMapper;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static tech.easyflow.ai.entity.DocumentCollection.KEY_DOC_RECALL_MAX_NUM;
import static tech.easyflow.ai.entity.DocumentCollection.KEY_SIMILARITY_THRESHOLD;

/**
 * {@link DocumentCollectionServiceImpl} 回归测试。
 *
 * @author Codex
 * @since 2026-04-15
 */
public class DocumentCollectionServiceImplTest {

    /**
     * 验证检索结果会在重排前过滤掉未完成文档，避免高分进行中文档挤占最终名额。
     *
     * @throws Exception 反射注入异常
     */
    @Test
    public void searchShouldFilterNonCompletedChunksBeforeFinalTopK() throws Exception {
        BigInteger knowledgeId = BigInteger.ONE;
        BigInteger completedDocumentId = BigInteger.valueOf(101);
        BigInteger indexingDocumentId = BigInteger.valueOf(102);
        BigInteger completedChunkId = BigInteger.valueOf(1001);
        BigInteger indexingChunkId = BigInteger.valueOf(1002);

        tech.easyflow.ai.entity.DocumentCollection collection = new tech.easyflow.ai.entity.DocumentCollection();
        collection.setId(knowledgeId);
        collection.setCollectionType(tech.easyflow.ai.entity.DocumentCollection.TYPE_DOCUMENT);
        collection.setOptions(new HashMap<String, Object>() {{
            put(KEY_DOC_RECALL_MAX_NUM, 1);
            put(KEY_SIMILARITY_THRESHOLD, BigDecimal.ZERO);
        }});

        tech.easyflow.ai.entity.DocumentChunk completedChunk = new tech.easyflow.ai.entity.DocumentChunk();
        completedChunk.setId(completedChunkId);
        completedChunk.setDocumentId(completedDocumentId);
        completedChunk.setDocumentCollectionId(knowledgeId);
        completedChunk.setContent("completed chunk");

        tech.easyflow.ai.entity.DocumentChunk indexingChunk = new tech.easyflow.ai.entity.DocumentChunk();
        indexingChunk.setId(indexingChunkId);
        indexingChunk.setDocumentId(indexingDocumentId);
        indexingChunk.setDocumentCollectionId(knowledgeId);
        indexingChunk.setContent("indexing chunk");

        tech.easyflow.ai.entity.Document completedDocument = new tech.easyflow.ai.entity.Document();
        completedDocument.setId(completedDocumentId);
        completedDocument.setCollectionId(knowledgeId);
        completedDocument.setProcessStatus(DocumentProcessStatus.COMPLETED.name());
        completedDocument.setTitle("completed");

        TestKeywordSearcher searcher = new TestKeywordSearcher(List.of(
            buildHit(indexingChunkId, 0.99D),
            buildHit(completedChunkId, 0.75D)
        ));

        DocumentCollectionServiceImpl service = new TestDocumentCollectionService(collection);
        setField(service, "searcherFactory", new SearcherFactory(new StaticObjectProvider<DocumentSearcher>(searcher)));
        setField(service, "documentChunkMapper", mockDocumentChunkMapper(completedChunk, indexingChunk));
        setField(service, "documentMapper", mockDocumentMapper(completedDocument));

        tech.easyflow.ai.rag.KnowledgeRetrievalRequest request = new tech.easyflow.ai.rag.KnowledgeRetrievalRequest();
        request.setKnowledgeId(knowledgeId);
        request.setQuery("test-query");
        request.setRetrievalMode(com.easyagents.rag.retrieval.RetrievalMode.KEYWORD);

        List<Document> result = service.search(request);

        Assert.assertEquals("内部关键词召回应扩容到业务 topK 的 5 倍", 5, searcher.lastRequestCount);
        Assert.assertEquals("知识库过滤后只应保留完成态文档", 1, result.size());
        Assert.assertEquals(completedChunkId, result.get(0).getId());
        Assert.assertEquals("completed chunk", result.get(0).getContent());
        Assert.assertEquals(String.valueOf(knowledgeId), searcher.lastKnowledgeId);
    }

    private static Document buildHit(BigInteger id, double score) {
        Document document = new Document();
        document.setId(id);
        document.setScore(score);
        document.setContent("raw-hit-" + id);
        return document;
    }

    private static DocumentChunkMapper mockDocumentChunkMapper(tech.easyflow.ai.entity.DocumentChunk... chunks) {
        Map<String, tech.easyflow.ai.entity.DocumentChunk> chunkMap = new HashMap<String, tech.easyflow.ai.entity.DocumentChunk>();
        for (tech.easyflow.ai.entity.DocumentChunk chunk : chunks) {
            chunkMap.put(String.valueOf(chunk.getId()), chunk);
        }
        return (DocumentChunkMapper) Proxy.newProxyInstance(
            DocumentChunkMapper.class.getClassLoader(),
            new Class<?>[]{DocumentChunkMapper.class},
            (proxy, method, args) -> {
                if ("selectListByQuery".equals(method.getName())) {
                    return List.copyOf(chunkMap.values());
                }
                return defaultValue(method.getReturnType());
            }
        );
    }

    private static DocumentMapper mockDocumentMapper(tech.easyflow.ai.entity.Document completedDocument) {
        return (DocumentMapper) Proxy.newProxyInstance(
            DocumentMapper.class.getClassLoader(),
            new Class<?>[]{DocumentMapper.class},
            (proxy, method, args) -> {
                if ("selectListByQuery".equals(method.getName())) {
                    return List.of(completedDocument);
                }
                return defaultValue(method.getReturnType());
            }
        );
    }

    private static void setField(Object target, String fieldName, Object value) throws Exception {
        Field field = DocumentCollectionServiceImpl.class.getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }

    private static Object defaultValue(Class<?> returnType) {
        if (returnType == boolean.class) {
            return false;
        }
        if (returnType == int.class) {
            return 0;
        }
        if (returnType == long.class) {
            return 0L;
        }
        return null;
    }

    /**
     * 固定返回测试知识库实体，避免依赖数据库。
     */
    private static class TestDocumentCollectionService extends DocumentCollectionServiceImpl {

        private final tech.easyflow.ai.entity.DocumentCollection collection;

        private TestDocumentCollectionService(tech.easyflow.ai.entity.DocumentCollection collection) {
            this.collection = collection;
        }

        @Override
        public tech.easyflow.ai.entity.DocumentCollection getById(Serializable id) {
            return collection;
        }
    }

    /**
     * 记录关键词检索请求参数的搜索器桩实现。
     */
    private static class TestKeywordSearcher implements DocumentSearcher {

        private final List<Document> documents;
        private int lastRequestCount;
        private String lastKnowledgeId;

        private TestKeywordSearcher(List<Document> documents) {
            this.documents = documents;
        }

        @Override
        public boolean addDocument(Document document) {
            return true;
        }

        @Override
        public boolean deleteDocument(Object id) {
            return true;
        }

        @Override
        public boolean updateDocument(Document document) {
            return true;
        }

        @Override
        public List<Document> searchDocuments(KeywordSearchRequest request) {
            this.lastRequestCount = request.getCount();
            this.lastKnowledgeId = request.getKnowledgeId();
            return documents;
        }
    }

    /**
     * 最小 ObjectProvider 实现，仅服务搜索器工厂测试注入。
     */
    private static class StaticObjectProvider<T> implements ObjectProvider<T> {

        private final T value;

        private StaticObjectProvider(T value) {
            this.value = value;
        }

        @Override
        public T getObject(Object... args) {
            return value;
        }

        @Override
        public T getIfAvailable() {
            return value;
        }

        @Override
        public T getIfUnique() {
            return value;
        }

        @Override
        public T getObject() {
            return value;
        }
    }
}
