package com.easyagents.search.engine.lucene;

import com.easyagents.core.document.Document;
import com.easyagents.search.engine.service.KeywordSearchMetadataKeys;
import com.easyagents.search.engine.service.KeywordSearchRequest;
import org.junit.Assert;
import org.junit.Test;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * {@link LuceneSearcher} 回归测试。
 */
public class LuceneSearcherTest {

    /**
     * 验证知识库过滤同时覆盖标题和正文。
     *
     * @throws Exception 临时目录或 Lucene 资源操作失败时抛出
     */
    @Test
    public void shouldFilterByKnowledgeIdAndSearchTitleAndContent() throws Exception {
        Path tempDir = Files.createTempDirectory("lucene-searcher-test");
        LuceneConfig config = new LuceneConfig();
        config.setIndexDirPath(tempDir.toString());
        try (LuceneSearcher searcher = new LuceneSearcher(config)) {
            Document first = new Document();
            first.setId("1");
            first.setTitle("客服标题");
            first.setContent("这里没有关键字");
            first.addMetadata(KeywordSearchMetadataKeys.KNOWLEDGE_ID, "100");

            Document second = new Document();
            second.setId("2");
            second.setTitle("别的知识库");
            second.setContent("客服内容");
            second.addMetadata(KeywordSearchMetadataKeys.KNOWLEDGE_ID, "200");

            Assert.assertTrue(searcher.addDocument(first));
            Assert.assertTrue(searcher.addDocument(second));

            KeywordSearchRequest request = KeywordSearchRequest.of("客服", 10);
            request.setKnowledgeId("100");
            List<Document> results = searcher.searchDocuments(request);

            Assert.assertEquals(1, results.size());
            Assert.assertEquals("1", String.valueOf(results.get(0).getId()));
            Assert.assertEquals("100", String.valueOf(
                results.get(0).getMetadata(KeywordSearchMetadataKeys.KNOWLEDGE_ID)));
        }
    }

    /**
     * 验证多个导入线程可共享同一个 IndexWriter 完成批量写入。
     *
     * @throws Exception 并发执行或 Lucene 资源操作失败时抛出
     */
    @Test
    public void shouldWriteConcurrentBatchesWithoutLockConflict() throws Exception {
        Path tempDir = Files.createTempDirectory("lucene-searcher-concurrent-test");
        LuceneConfig config = new LuceneConfig();
        config.setIndexDirPath(tempDir.toString());
        int workerCount = 4;
        int documentsPerWorker = 20;
        ExecutorService executor = Executors.newFixedThreadPool(workerCount);
        try (LuceneSearcher searcher = new LuceneSearcher(config)) {
            CountDownLatch startSignal = new CountDownLatch(1);
            List<Future<Boolean>> futures = new ArrayList<>();
            for (int worker = 0; worker < workerCount; worker++) {
                int currentWorker = worker;
                futures.add(executor.submit(() -> {
                    startSignal.await();
                    List<Document> documents = new ArrayList<>();
                    for (int offset = 0; offset < documentsPerWorker; offset++) {
                        Document document = new Document();
                        document.setId(currentWorker + "-" + offset);
                        document.setContent("concurrent indexing content");
                        document.addMetadata(KeywordSearchMetadataKeys.KNOWLEDGE_ID, "concurrent-kb");
                        documents.add(document);
                    }
                    return searcher.addDocuments(documents);
                }));
            }
            startSignal.countDown();
            for (Future<Boolean> future : futures) {
                Assert.assertTrue(future.get());
            }

            int expectedCount = workerCount * documentsPerWorker;
            KeywordSearchRequest request = KeywordSearchRequest.of("concurrent", expectedCount);
            request.setKnowledgeId("concurrent-kb");
            Assert.assertEquals(expectedCount, searcher.searchDocuments(request).size());
        } finally {
            executor.shutdownNow();
        }
    }

    /**
     * 验证相同文档 ID 重试写入时执行覆盖而不会保留重复旧索引。
     *
     * @throws Exception 临时目录或 Lucene 资源操作失败时抛出
     */
    @Test
    public void shouldUpsertDocumentByIdOnRetry() throws Exception {
        Path tempDir = Files.createTempDirectory("lucene-searcher-upsert-test");
        LuceneConfig config = new LuceneConfig();
        config.setIndexDirPath(tempDir.toString());
        try (LuceneSearcher searcher = new LuceneSearcher(config)) {
            Document document = new Document();
            document.setId("retry-id");
            document.setContent("oldkeyword");
            Assert.assertTrue(searcher.addDocument(document));

            document.setContent("newkeyword");
            Assert.assertTrue(searcher.addDocument(document));

            Assert.assertTrue(searcher.searchDocuments("oldkeyword", 10).isEmpty());
            Assert.assertEquals(1, searcher.searchDocuments("newkeyword", 10).size());
        }
    }

    /**
     * 验证多个文档可在一次提交中批量删除。
     *
     * @throws Exception 临时目录或 Lucene 资源操作失败时抛出
     */
    @Test
    public void shouldDeleteDocumentsInSingleBatch() throws Exception {
        Path tempDir = Files.createTempDirectory("lucene-searcher-delete-test");
        LuceneConfig config = new LuceneConfig();
        config.setIndexDirPath(tempDir.toString());
        try (LuceneSearcher searcher = new LuceneSearcher(config)) {
            Document first = new Document();
            first.setId("delete-first");
            first.setContent("batchdelete");
            Document second = new Document();
            second.setId("delete-second");
            second.setContent("batchdelete");

            Assert.assertTrue(searcher.addDocuments(List.of(first, second)));
            Assert.assertTrue(searcher.deleteDocuments(List.of(first.getId(), second.getId())));
            Assert.assertTrue(searcher.searchDocuments("batchdelete", 10).isEmpty());
        }
    }
}
