package tech.easyflow.publicapi.controller;

import cn.hutool.core.io.IoUtil;
import com.easyagents.core.model.embedding.EmbeddingModel;
import com.easyagents.core.store.DocumentStore;
import com.easyagents.core.store.StoreOptions;
import com.easyagents.core.store.StoreResult;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.ai.documentimport.DocumentImportDtos;
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
import tech.easyflow.ai.entity.Document;
import tech.easyflow.ai.entity.DocumentChunk;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.FaqItem;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.enums.KnowledgeShareActionScope;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.DocumentService;
import tech.easyflow.ai.service.FaqCategoryService;
import tech.easyflow.ai.service.FaqItemService;
import tech.easyflow.ai.service.KnowledgeShareAuditService;
import tech.easyflow.ai.service.KnowledgeSharePermissionService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.impl.KnowledgeSharePermissionServiceImpl;
import tech.easyflow.ai.support.DocumentStoreLifecycleSupport;
import tech.easyflow.ai.vo.FaqImportResultVo;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.service.SysApiKeyService;

import javax.annotation.Resource;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 知识库 API 分享接口。
 */
@RestController
@RequestMapping(value = "/public-api/knowledge-share", produces = MediaType.APPLICATION_JSON_VALUE)
public class PublicKnowledgeShareController {

    @Resource
    private SysApiKeyService sysApiKeyService;
    @Resource
    private KnowledgeSharePermissionService knowledgeSharePermissionService;
    @Resource
    private KnowledgeShareAuditService knowledgeShareAuditService;
    @Resource
    private DocumentCollectionService documentCollectionService;
    @Resource
    private DocumentService documentService;
    @Resource
    private DocumentChunkService documentChunkService;
    @Resource
    private FaqItemService faqItemService;
    @Resource
    private FaqCategoryService faqCategoryService;
    @Resource
    private ModelService modelService;
    @Resource(name = "default")
    private FileStorageService fileStorageService;

    /**
     * 获取知识库详情。
     */
    @GetMapping("/detail")
    public Result<DocumentCollection> detail(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        audit(apiKey, "API读取知识库详情", "KNOWLEDGE_API_SHARE_ACCESS", request.getRequestURI(), Map.of("knowledgeId", knowledgeId));
        return Result.ok(documentCollectionService.getDetail(knowledgeId.toString()));
    }

    /**
     * 检索知识库。
     */
    @GetMapping("/search")
    public Result<List<KnowledgeSearchResultItem>> search(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam String keyword,
        @RequestParam(required = false) String retrievalMode,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.SEARCH.name());
        KnowledgeRetrievalRequest retrievalRequest = new KnowledgeRetrievalRequest();
        retrievalRequest.setKnowledgeId(knowledgeId);
        retrievalRequest.setQuery(keyword);
        retrievalRequest.setRetrievalMode(KnowledgeRetrievalModes.parse(retrievalMode));
        retrievalRequest.setCallerType("PUBLIC_API");
        retrievalRequest.setCallerId(String.valueOf(knowledgeId));
        audit(apiKey, "API检索知识库", "KNOWLEDGE_API_SHARE_ACCESS", request.getRequestURI(), Map.of("knowledgeId", knowledgeId));
        return Result.ok(toKnowledgeSearchResult(documentCollectionService.search(retrievalRequest)));
    }

    /**
     * 文档分页。
     */
    @GetMapping("/document/page")
    public Result<Page<Document>> documentPage(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam(required = false) String title,
        @RequestParam(defaultValue = "10") int pageSize,
        @RequestParam(defaultValue = "1") int pageNumber,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        requireDocumentKnowledge(knowledgeId);
        return Result.ok(documentService.getDocumentList(knowledgeId.toString(), pageSize, pageNumber, title));
    }

    /**
     * 下载文档。
     */
    @GetMapping(value = "/document/download", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void documentDownload(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam BigInteger documentId,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws Exception {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        requireDocumentKnowledge(knowledgeId);
        Document document = requireDocument(documentId, knowledgeId);
        response.setContentType("application/octet-stream");
        response.setCharacterEncoding(StandardCharsets.UTF_8.name());
        String fileName = URLEncoder.encode(document.getTitle(), StandardCharsets.UTF_8).replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName);
        try (InputStream inputStream = fileStorageService.readStream(document.getDocumentPath())) {
            IoUtil.copy(inputStream, response.getOutputStream());
            response.flushBuffer();
        }
        audit(apiKey, "API下载文档", "KNOWLEDGE_API_SHARE_ACCESS", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "documentId", documentId));
    }

    /**
     * 删除文档。
     */
    @PostMapping("/document/remove")
    public Result<?> removeDocument(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @JsonBody("id") String id,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_DELETE.name());
        requireDocumentKnowledge(knowledgeId);
        requireDocument(new BigInteger(id), knowledgeId);
        audit(apiKey, "API删除文档", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "documentId", id));
        return Result.ok(documentService.removeDoc(id));
    }

    /**
     * 文档导入分析。
     */
    @PostMapping("/document/import/analyze")
    public Result<DocumentImportDtos.AnalyzeResponse> analyzeImport(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.AnalyzeRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API分析文档导入", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.analyzeImport(request);
    }

    /**
     * 文档导入预览。
     */
    @PostMapping("/document/import/preview")
    public Result<DocumentImportDtos.PreviewResponse> previewImport(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.PreviewRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API预览文档导入", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.previewImport(request);
    }

    /**
     * 文档导入提交。
     */
    @PostMapping("/document/import/commit")
    public Result<DocumentImportDtos.CommitResponse> commitImport(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.CommitRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API提交文档导入", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.commitImport(request);
    }

    @PostMapping("/document/import/task/create")
    public Result<DocumentImportDtos.TaskCreateResponse> createImportTask(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.TaskCreateRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API创建文档导入任务", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.createImportTask(request);
    }

    @GetMapping("/document/import/task/detail")
    public Result<DocumentImportDtos.TaskDetailResponse> getImportTaskDetail(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam BigInteger taskId,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        requireDocumentKnowledge(knowledgeId);
        Result<DocumentImportDtos.TaskDetailResponse> result = documentService.getImportTaskDetail(taskId);
        if (result.getData() == null || result.getData().getKnowledgeId() == null
            || result.getData().getKnowledgeId().compareTo(knowledgeId) != 0) {
            throw new BusinessException("任务不存在");
        }
        return result;
    }

    @PostMapping("/document/import/task/preview")
    public Result<DocumentImportDtos.PreviewResponse> previewImportTask(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.PreviewRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API预览文档分块", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.previewImportTask(request);
    }

    @PostMapping("/document/import/task/startIndex")
    public Result<DocumentImportDtos.TaskStartIndexResponse> startIndexTask(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.TaskStartIndexRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API启动文档向量化", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.startIndexTask(request);
    }

    @PostMapping("/document/import/task/retryParse")
    public Result<DocumentImportDtos.TaskStartIndexResponse> retryParseTask(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.TaskRetryRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API重试文档解析", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.retryParseTask(request);
    }

    @PostMapping("/document/import/task/retryIndex")
    public Result<DocumentImportDtos.TaskStartIndexResponse> retryIndexTask(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody DocumentImportDtos.TaskRetryRequest request,
        HttpServletRequest servletRequest
    ) {
        assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireDocumentKnowledge(request.getKnowledgeId());
        audit(apiKey, "API重试文档向量化", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
        return documentService.retryIndexTask(request);
    }

    /**
     * Chunk 分页。
     */
    @GetMapping("/documentChunk/page")
    public Result<Page<DocumentChunk>> documentChunkPage(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam BigInteger documentId,
        @RequestParam(defaultValue = "1") long pageNumber,
        @RequestParam(defaultValue = "10") long pageSize,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        requireDocumentKnowledge(knowledgeId);
        requireDocument(documentId, knowledgeId);
        QueryWrapper wrapper = QueryWrapper.create()
            .eq(DocumentChunk::getDocumentId, documentId)
            .orderBy("sorting asc");
        return Result.ok(documentChunkService.page(new Page<>(pageNumber, pageSize), wrapper));
    }

    /**
     * 更新 Chunk。
     */
    @PostMapping("/documentChunk/update")
    public Result<?> updateDocumentChunk(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @JsonBody DocumentChunk documentChunk,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_UPDATE.name());
        requireDocumentKnowledge(knowledgeId);
        DocumentChunk current = requireDocumentChunk(documentChunk.getId(), knowledgeId);
        boolean success = documentChunkService.updateById(documentChunk);
        if (success) {
            DocumentCollection knowledge = documentCollectionService.getById(knowledgeId);
            DocumentStore documentStore = knowledge.toDocumentStore();
            if (documentStore == null) {
                return Result.fail(2, "知识库没有配置向量库");
            }
            try {
                Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId());
                if (model == null) {
                    return Result.fail(3, "知识库没有配置向量模型");
                }
                EmbeddingModel embeddingModel = model.toEmbeddingModel();
                documentStore.setEmbeddingModel(embeddingModel);
                StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection());
                com.easyagents.core.document.Document doc = com.easyagents.core.document.Document.of(documentChunk.getContent());
                doc.setId(current.getId());
                StoreResult result = documentStore.update(doc, options);
                audit(apiKey, "API更新文档 Chunk", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", documentChunk.getId()));
                return Result.ok(result);
            } finally {
                DocumentStoreLifecycleSupport.closeQuietly(documentStore);
            }
        }
        return Result.ok(false);
    }

    /**
     * 删除 Chunk。
     */
    @PostMapping("/documentChunk/remove")
    public Result<?> removeDocumentChunk(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @JsonBody("id") BigInteger chunkId,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_DELETE.name());
        requireDocumentKnowledge(knowledgeId);
        requireDocumentChunk(chunkId, knowledgeId);
        DocumentCollection knowledge = documentCollectionService.getById(knowledgeId);
        DocumentStore documentStore = knowledge.toDocumentStore();
        if (documentStore == null) {
            return Result.fail(2, "知识库没有配置向量库");
        }
        try {
            Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId());
            if (model == null) {
                return Result.fail(3, "知识库没有配置向量模型");
            }
            documentStore.setEmbeddingModel(model.toEmbeddingModel());
            StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection());
            documentStore.delete(Collections.singletonList(chunkId), options);
            documentChunkService.removeById(chunkId);
            audit(apiKey, "API删除文档 Chunk", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", chunkId));
            return Result.ok(true);
        } finally {
            DocumentStoreLifecycleSupport.closeQuietly(documentStore);
        }
    }

    /**
     * FAQ 分页。
     */
    @GetMapping("/faq/page")
    public Result<Page<FaqItem>> faqPage(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam(required = false) String question,
        @RequestParam(required = false) String categoryId,
        @RequestParam(defaultValue = "1") long pageNumber,
        @RequestParam(defaultValue = "10") long pageSize,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        requireFaqKnowledge(knowledgeId);
        faqCategoryService.ensureDefaultCategory(knowledgeId);
        QueryWrapper queryWrapper = QueryWrapper.create()
            .eq(FaqItem::getCollectionId, knowledgeId);
        if (question != null && !question.isBlank()) {
            queryWrapper.like(FaqItem::getQuestion, question.trim());
        }
        if (categoryId != null && !categoryId.isBlank()) {
            List<BigInteger> descendantIds = faqCategoryService.findDescendantIds(knowledgeId, new BigInteger(categoryId));
            if (descendantIds.isEmpty()) {
                queryWrapper.eq(FaqItem::getId, BigInteger.ZERO);
            } else {
                queryWrapper.in(FaqItem::getCategoryId, descendantIds);
            }
        }
        queryWrapper.orderBy("order_no asc");
        Page<FaqItem> page = faqItemService.page(new Page<>(pageNumber, pageSize), queryWrapper);
        Map<BigInteger, String> pathMap = faqCategoryService.buildPathMap(knowledgeId);
        if (page.getRecords() != null) {
            for (FaqItem record : page.getRecords()) {
                record.setCategoryPath(pathMap.get(record.getCategoryId()));
            }
        }
        return Result.ok(page);
    }

    /**
     * FAQ 详情。
     */
    @GetMapping("/faq/detail")
    public Result<FaqItem> faqDetail(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @RequestParam String id,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
        requireFaqKnowledge(knowledgeId);
        FaqItem faqItem = requireFaq(new BigInteger(id), knowledgeId);
        return Result.ok(faqItem);
    }

    /**
     * 新增 FAQ。
     */
    @PostMapping("/faq/save")
    public Result<?> saveFaq(
        @RequestHeader("ApiKey") String apiKey,
        @JsonBody FaqItem entity,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), entity.getCollectionId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
        requireFaqKnowledge(entity.getCollectionId());
        audit(apiKey, "API新增FAQ", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", entity.getCollectionId()));
        return Result.ok(faqItemService.saveFaqItem(entity));
    }

    /**
     * 更新 FAQ。
     */
    @PostMapping("/faq/update")
    public Result<?> updateFaq(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @JsonBody FaqItem entity,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_UPDATE.name());
        requireFaqKnowledge(knowledgeId);
        requireFaq(entity.getId(), knowledgeId);
        audit(apiKey, "API更新FAQ", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "faqId", entity.getId()));
        return Result.ok(faqItemService.updateFaqItem(entity));
    }

    /**
     * 删除 FAQ。
     */
    @PostMapping("/faq/remove")
    public Result<?> removeFaq(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        @JsonBody("id") BigInteger id,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_DELETE.name());
        requireFaqKnowledge(knowledgeId);
        requireFaq(id, knowledgeId);
        audit(apiKey, "API删除FAQ", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "faqId", id));
        return Result.ok(faqItemService.removeFaqItem(id));
    }

    /**
     * 导入 FAQ Excel。
     */
    @PostMapping(value = "/faq/importExcel", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Result<FaqImportResultVo> importFaqExcel(
        @RequestHeader("ApiKey") String apiKey,
        MultipartFile file,
        BigInteger collectionId,
        HttpServletRequest request
    ) {
        assertApiShare(apiKey, request.getRequestURI(), collectionId, KnowledgeShareActionScope.IMPORT_EXPORT.name());
        requireFaqKnowledge(collectionId);
        audit(apiKey, "API导入FAQ Excel", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", collectionId));
        return Result.ok(faqItemService.importFromExcel(collectionId, file));
    }

    /**
     * 下载 FAQ 导入模板。
     */
    @GetMapping(value = "/faq/downloadImportTemplate", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void downloadFaqImportTemplate(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws Exception {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.IMPORT_EXPORT.name());
        requireFaqKnowledge(knowledgeId);
        response.setContentType("application/octet-stream");
        response.setHeader(
            "Content-disposition",
            "attachment;filename*=utf-8''" + URLEncoder.encode("faq_import_template.xlsx", StandardCharsets.UTF_8)
        );
        faqItemService.writeImportTemplate(response.getOutputStream());
        response.flushBuffer();
        audit(apiKey, "API下载FAQ导入模板", "KNOWLEDGE_API_SHARE_ACCESS", request.getRequestURI(), Map.of("knowledgeId", knowledgeId));
    }

    /**
     * 导出 FAQ Excel。
     */
    @GetMapping(value = "/faq/exportExcel", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void exportFaqExcel(
        @RequestHeader("ApiKey") String apiKey,
        @RequestParam BigInteger knowledgeId,
        HttpServletRequest request,
        HttpServletResponse response
    ) throws Exception {
        assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.IMPORT_EXPORT.name());
        requireFaqKnowledge(knowledgeId);
        String fileName = "faq_export_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xlsx";
        response.setContentType("application/octet-stream");
        response.setHeader(
            "Content-disposition",
            "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8)
        );
        faqItemService.exportToExcel(knowledgeId, response.getOutputStream());
        response.flushBuffer();
        audit(apiKey, "API导出FAQ Excel", "KNOWLEDGE_API_SHARE_ACCESS", request.getRequestURI(), Map.of("knowledgeId", knowledgeId));
    }

    private void assertApiShare(String apiKey, String requestUri, BigInteger knowledgeId, String actionScope) {
        SysApiKey sysApiKey = sysApiKeyService.getSysApiKey(apiKey);
        knowledgeSharePermissionService.assertApiShare(sysApiKey.getId(), requestUri, knowledgeId, actionScope);
    }

    /**
     * 断言知识库为文档类型。
     *
     * @param knowledgeId 知识库ID
     * @return 知识库实体
     */
    private DocumentCollection requireDocumentKnowledge(BigInteger knowledgeId) {
        DocumentCollection knowledge = requireKnowledge(knowledgeId);
        if (!knowledge.isDocumentCollection()) {
            throw new BusinessException("当前知识库类型不支持文档接口");
        }
        return knowledge;
    }

    /**
     * 断言知识库为 FAQ 类型。
     *
     * @param knowledgeId 知识库ID
     * @return 知识库实体
     */
    private DocumentCollection requireFaqKnowledge(BigInteger knowledgeId) {
        DocumentCollection knowledge = requireKnowledge(knowledgeId);
        if (!knowledge.isFaqCollection()) {
            throw new BusinessException("当前知识库类型不支持FAQ接口");
        }
        return knowledge;
    }

    /**
     * 获取知识库并保证存在。
     *
     * @param knowledgeId 知识库ID
     * @return 知识库实体
     */
    private DocumentCollection requireKnowledge(BigInteger knowledgeId) {
        DocumentCollection knowledge = documentCollectionService.getById(knowledgeId);
        if (knowledge == null) {
            throw new BusinessException("知识库不存在");
        }
        return knowledge;
    }

    private Document requireDocument(BigInteger documentId, BigInteger knowledgeId) {
        Document document = documentService.getById(documentId);
        if (document == null || document.getCollectionId() == null || document.getCollectionId().compareTo(knowledgeId) != 0) {
            throw new BusinessException("文档不存在");
        }
        return document;
    }

    private DocumentChunk requireDocumentChunk(BigInteger chunkId, BigInteger knowledgeId) {
        DocumentChunk chunk = documentChunkService.getById(chunkId);
        if (chunk == null || chunk.getDocumentCollectionId() == null || chunk.getDocumentCollectionId().compareTo(knowledgeId) != 0) {
            throw new BusinessException("记录不存在");
        }
        return chunk;
    }

    private FaqItem requireFaq(BigInteger faqId, BigInteger knowledgeId) {
        FaqItem faqItem = faqItemService.getById(faqId);
        if (faqItem == null || faqItem.getCollectionId() == null || faqItem.getCollectionId().compareTo(knowledgeId) != 0) {
            throw new BusinessException("FAQ不存在");
        }
        return faqItem;
    }

    private void audit(String apiKey, String actionName, String actionType, String actionUrl, Map<String, Object> detail) {
        SysApiKey sysApiKey = sysApiKeyService.getSysApiKey(apiKey);
        Map<String, Object> payload = new HashMap<>(detail);
        payload.put("apiKeyId", sysApiKey.getId());
        payload.put("channel", "API");
        knowledgeShareAuditService.log(null, actionName, actionType, actionUrl, payload);
    }

    private List<KnowledgeSearchResultItem> toKnowledgeSearchResult(List<com.easyagents.core.document.Document> documents) {
        List<KnowledgeSearchResultItem> result = new java.util.ArrayList<>();
        for (com.easyagents.core.document.Document document : documents) {
            KnowledgeSearchResultItem item = new KnowledgeSearchResultItem();
            item.setContent(document.getContent());
            Object renderMarkdown = document.getMetadata("renderMarkdown");
            item.setRenderMarkdown(renderMarkdown == null ? null : String.valueOf(renderMarkdown));
            Object sourceFileName = document.getMetadata("sourceFileName");
            item.setSourceFileName(sourceFileName == null ? null : String.valueOf(sourceFileName));
            item.setScore(document.getScore());
            Object hitSource = document.getMetadata("hitSource");
            item.setHitSource(hitSource == null ? null : String.valueOf(hitSource));
            item.setVectorScore(asDouble(document.getMetadata("vectorScore")));
            item.setKeywordScore(asDouble(document.getMetadata("keywordScore")));
            result.add(item);
        }
        return result;
    }

    private Double asDouble(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Number number) {
            return number.doubleValue();
        }
        return Double.parseDouble(String.valueOf(value));
    }
}
