package tech.easyflow.admin.controller.ai;

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.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
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.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.ai.documentimport.DocumentImportDtos;
import tech.easyflow.ai.documentimport.task.DocumentImportTaskStatusStreamService;
import tech.easyflow.ai.dto.KnowledgeShareLimitedConfigRequest;
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.DocumentCollectionSplitParams;
import tech.easyflow.ai.entity.FaqCategory;
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.KnowledgeEmbeddingService;
import tech.easyflow.ai.service.KnowledgeShareAuditService;
import tech.easyflow.ai.service.KnowledgeShareService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.support.DocumentStoreLifecycleSupport;
import tech.easyflow.ai.vo.FaqImportResultVo;
import tech.easyflow.ai.vo.KnowledgeShareAuthContext;
import tech.easyflow.ai.vo.KnowledgeShareViewDetail;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.vo.UploadResVo;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;

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

/**
 * 内部员工登录态下的知识库分享访问接口。
 */
@RestController
@RequestMapping("/api/v1/share/knowledge")
public class ShareKnowledgeController {

    private static final long MAX_IMAGE_SIZE_BYTES = 5L * 1024L * 1024L;
    private static final Set<String> ALLOWED_IMAGE_TYPES = new HashSet<>(Arrays.asList(
        "image/jpeg",
        "image/png",
        "image/webp",
        "image/gif"
    ));

    @Resource
    private KnowledgeShareService knowledgeShareService;
    @Resource
    private KnowledgeShareAuditService knowledgeShareAuditService;
    @Resource
    private DocumentCollectionService documentCollectionService;
    @Resource
    private DocumentService documentService;
    @Resource
    private DocumentChunkService documentChunkService;
    @Resource
    private ModelService modelService;
    @Resource
    private FaqItemService faqItemService;
    @Resource
    private FaqCategoryService faqCategoryService;
    @Resource
    private KnowledgeEmbeddingService knowledgeEmbeddingService;
    @Resource(name = "default")
    private FileStorageService fileStorageService;
    @Resource
    private DocumentImportTaskStatusStreamService documentImportTaskStatusStreamService;

    /**
     * 获取知识库详情。
     *
     * @param shareKey 分享访问密钥
     * @return 知识库详情
     */
    @GetMapping("/documentCollection/detail")
    public Result<KnowledgeShareViewDetail> detail(@RequestParam String shareKey) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        audit(context, "访问知识库分享页", "KNOWLEDGE_SHARE_URL_ACCESS", false, auditDetail("knowledgeId", context.getKnowledge().getId()));
        KnowledgeShareViewDetail detail = new KnowledgeShareViewDetail();
        detail.setKnowledge(context.getKnowledge());
        detail.setPermissionScopes(new java.util.ArrayList<String>(context.getShare().getPermissionScopeSet()));
        return Result.ok(detail);
    }

    /**
     * 获取模型列表。
     *
     * @param shareKey 分享访问密钥
     * @param modelType 模型类型
     * @return 模型列表
     */
    @GetMapping("/documentCollection/modelList")
    public Result<List<Model>> modelList(
        @RequestParam String shareKey,
        @RequestParam String modelType
    ) {
        knowledgeShareService.assertUrlShareAccess(shareKey, null, KnowledgeShareActionScope.CONFIG_UPDATE.name());
        Model entity = new Model();
        entity.setModelType(modelType);
        return Result.ok(modelService.listSelectableModels(entity, false, null, null));
    }

    /**
     * 更新受限配置。
     *
     * @param request 更新请求
     * @param shareKey 分享访问密钥
     * @return 结果
     */
    @PostMapping("/documentCollection/shareConfigUpdate")
    public Result<Void> updateConfig(
        @JsonBody KnowledgeShareLimitedConfigRequest request,
        @RequestParam String shareKey
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONFIG_UPDATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        DocumentCollection current = context.getKnowledge();
        DocumentCollection update = new DocumentCollection();
        update.setId(current.getId());
        Map<String, Object> options = current.getOptions() == null
            ? new HashMap<>()
            : new HashMap<>(current.getOptions());
        if (request.getVectorEmbedModelId() != null) {
            update.setVectorEmbedModelId(request.getVectorEmbedModelId());
            options.put(DocumentCollection.KEY_CAN_UPDATE_EMBEDDING_MODEL, true);
            Model model = modelService.getModelInstance(request.getVectorEmbedModelId());
            if (model != null) {
                update.setDimensionOfVectorModel(Model.getEmbeddingDimension(model.toEmbeddingModel()));
            }
        }
        if (request.getRerankModelId() != null || current.getRerankModelId() != null) {
            update.setRerankModelId(request.getRerankModelId());
        }
        if (request.getRerankEnable() != null) {
            options.put(DocumentCollection.KEY_RERANK_ENABLE, request.getRerankEnable());
        }
        if (request.getDocRecallMaxNum() != null) {
            options.put(DocumentCollection.KEY_DOC_RECALL_MAX_NUM, request.getDocRecallMaxNum());
        }
        if (request.getSimThreshold() != null) {
            options.put(DocumentCollection.KEY_SIMILARITY_THRESHOLD, BigDecimal.valueOf(request.getSimThreshold()));
        }
        update.setOptions(options);
        documentCollectionService.updateById(update);
        if (Boolean.TRUE.equals(request.getRebuildVectors())) {
            knowledgeEmbeddingService.rebuildKnowledgeVectors(knowledgeId);
        }
        audit(context, "更新知识库分享受限配置", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return Result.ok();
    }

    /**
     * 检索知识库。
     *
     * @param shareKey 分享访问密钥
     * @param keyword 检索关键词
     * @param retrievalMode 检索模式
     * @return 检索结果
     */
    @GetMapping("/documentCollection/search")
    public Result<List<KnowledgeSearchResultItem>> search(
        @RequestParam String shareKey,
        @RequestParam String keyword,
        @RequestParam(required = false) String retrievalMode
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.SEARCH.name()
        );
        KnowledgeRetrievalRequest request = new KnowledgeRetrievalRequest();
        request.setKnowledgeId(context.getKnowledge().getId());
        request.setQuery(keyword);
        request.setRetrievalMode(KnowledgeRetrievalModes.parse(retrievalMode));
        request.setCallerType("SHARE_URL");
        request.setCallerId(String.valueOf(context.getShare().getId()));
        return Result.ok(toKnowledgeSearchResult(documentCollectionService.search(request)));
    }

    /**
     * 文档分页。
     */
    @GetMapping("/document/documentList")
    public Result<Page<Document>> documentPage(
        @RequestParam String shareKey,
        @RequestParam(required = false) String title,
        @RequestParam(defaultValue = "10") int pageSize,
        @RequestParam(defaultValue = "1") int pageNumber
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        return Result.ok(documentService.getDocumentList(context.getKnowledge().getId().toString(), pageSize, pageNumber, title));
    }

    /**
     * 订阅分享知识库的文档任务状态流。
     *
     * @param shareKey 分享访问密钥
     * @param knowledgeId 知识库 ID
     * @return SSE 推送连接
     */
    @PostMapping(value = "/document/import/task/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter streamDocumentTask(
        @RequestParam String shareKey,
        @JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            knowledgeId,
            KnowledgeShareActionScope.VIEW.name()
        );
        return documentImportTaskStatusStreamService.subscribe(context.getKnowledge().getId());
    }

    /**
     * 下载文档。
     */
    @GetMapping("/document/download")
    public void documentDownload(
        @RequestParam String shareKey,
        @RequestParam BigInteger documentId,
        HttpServletResponse response
    ) throws Exception {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        Document document = documentService.getById(documentId);
        if (document == null || document.getCollectionId() == null
            || document.getCollectionId().compareTo(context.getKnowledge().getId()) != 0) {
            throw new BusinessException("文档不存在");
        }
        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();
        }
    }

    /**
     * 删除文档。
     */
    @PostMapping("/document/removeDoc")
    public Result<?> removeDocument(
        @RequestParam String shareKey,
        @JsonBody("id") String id
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_DELETE.name()
        );
        Document document = documentService.getById(new BigInteger(id));
        if (document == null || document.getCollectionId() == null
            || document.getCollectionId().compareTo(context.getKnowledge().getId()) != 0) {
            throw new BusinessException("文档不存在");
        }
        boolean success = documentService.removeDoc(id);
        audit(context, "删除分享文档", "KNOWLEDGE_SHARE_URL_WRITE", true,
            auditDetail("knowledgeId", context.getKnowledge().getId(), "documentId", id));
        return Result.ok(success);
    }

    /**
     * 文档导入分析。
     */
    @PostMapping("/document/import/analyze")
    public Result<DocumentImportDtos.AnalyzeResponse> analyzeImport(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.AnalyzeRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "分析分享文档导入", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.analyzeImport(request);
    }

    /**
     * 文档导入预览。
     */
    @PostMapping("/document/import/preview")
    public Result<DocumentImportDtos.PreviewResponse> previewImport(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.PreviewRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "预览分享文档导入", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.previewImport(request);
    }

    /**
     * 提交文档导入。
     */
    @PostMapping("/document/import/commit")
    public Result<DocumentImportDtos.CommitResponse> commitImport(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.CommitRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "提交分享文档导入", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.commitImport(request);
    }

    @PostMapping("/document/import/task/create")
    public Result<DocumentImportDtos.TaskCreateResponse> createImportTask(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.TaskCreateRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "创建分享文档导入任务", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.createImportTask(request);
    }

    @GetMapping("/document/import/task/detail")
    public Result<DocumentImportDtos.TaskDetailResponse> getImportTaskDetail(
        @RequestParam String shareKey,
        @RequestParam BigInteger taskId
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        Result<DocumentImportDtos.TaskDetailResponse> result = documentService.getImportTaskDetail(taskId);
        BigInteger knowledgeId = result.getData() == null ? null : result.getData().getKnowledgeId();
        if (knowledgeId == null || knowledgeId.compareTo(context.getKnowledge().getId()) != 0) {
            throw new BusinessException("任务不存在");
        }
        return result;
    }

    @PostMapping("/document/import/task/preview")
    public Result<DocumentImportDtos.PreviewResponse> previewImportTask(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.PreviewRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "预览分享文档分块", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.previewImportTask(request);
    }

    @PostMapping("/document/import/task/startIndex")
    public Result<DocumentImportDtos.TaskStartIndexResponse> startIndexTask(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.TaskStartIndexRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "启动分享文档向量化", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.startIndexTask(request);
    }

    @PostMapping("/document/import/task/retryParse")
    public Result<DocumentImportDtos.TaskStartIndexResponse> retryParseTask(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.TaskRetryRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "重试分享文档解析", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.retryParseTask(request);
    }

    @PostMapping("/document/import/task/retryIndex")
    public Result<DocumentImportDtos.TaskStartIndexResponse> retryIndexTask(
        @RequestParam String shareKey,
        @JsonBody DocumentImportDtos.TaskRetryRequest request
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            request == null ? null : request.getKnowledgeId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        BigInteger knowledgeId = resolveKnowledgeId(context, request == null ? null : request.getKnowledgeId());
        request.setKnowledgeId(knowledgeId);
        audit(context, "重试分享文档向量化", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", knowledgeId));
        return documentService.retryIndexTask(request);
    }

    /**
     * Chunk 分页。
     */
    @GetMapping("/documentChunk/page")
    public Result<Page<DocumentChunk>> documentChunkPage(
        @RequestParam String shareKey,
        @RequestParam BigInteger documentId,
        @RequestParam(defaultValue = "1") long pageNumber,
        @RequestParam(defaultValue = "10") long pageSize
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        Document document = documentService.getById(documentId);
        if (document == null || document.getCollectionId() == null
            || document.getCollectionId().compareTo(context.getKnowledge().getId()) != 0) {
            throw new BusinessException("文档不存在");
        }
        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(
        @RequestParam String shareKey,
        @JsonBody DocumentChunk documentChunk
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_UPDATE.name()
        );
        DocumentChunk current = documentChunkService.getById(documentChunk.getId());
        if (current == null || current.getDocumentCollectionId() == null
            || current.getDocumentCollectionId().compareTo(context.getKnowledge().getId()) != 0) {
            throw new BusinessException("记录不存在");
        }
        boolean success = documentChunkService.updateById(documentChunk);
        if (success) {
            DocumentStore documentStore = context.getKnowledge().toDocumentStore();
            if (documentStore == null) {
                return Result.fail(2, "知识库没有配置向量库");
            }
            try {
                Model model = modelService.getModelInstance(context.getKnowledge().getVectorEmbedModelId());
                if (model == null) {
                    return Result.fail(3, "知识库没有配置向量模型");
                }
                EmbeddingModel embeddingModel = model.toEmbeddingModel();
                documentStore.setEmbeddingModel(embeddingModel);
                StoreOptions options = StoreOptions.ofCollectionName(context.getKnowledge().getVectorStoreCollection());
                com.easyagents.core.document.Document doc = com.easyagents.core.document.Document.of(documentChunk.getContent());
                doc.setId(documentChunk.getId());
                StoreResult result = documentStore.update(doc, options);
                audit(context, "更新分享文档 Chunk", "KNOWLEDGE_SHARE_URL_WRITE", true,
                    auditDetail("knowledgeId", context.getKnowledge().getId(), "chunkId", documentChunk.getId()));
                return Result.ok(result);
            } finally {
                DocumentStoreLifecycleSupport.closeQuietly(documentStore);
            }
        }
        return Result.ok(false);
    }

    /**
     * 删除 Chunk。
     */
    @PostMapping("/documentChunk/removeChunk")
    public Result<?> removeDocumentChunk(
        @RequestParam String shareKey,
        @JsonBody("id") BigInteger chunkId
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_DELETE.name()
        );
        DocumentChunk current = documentChunkService.getById(chunkId);
        if (current == null || current.getDocumentCollectionId() == null
            || current.getDocumentCollectionId().compareTo(context.getKnowledge().getId()) != 0) {
            return Result.fail(1, "记录不存在");
        }
        DocumentStore documentStore = context.getKnowledge().toDocumentStore();
        if (documentStore == null) {
            return Result.fail(2, "知识库没有配置向量库");
        }
        try {
            Model model = modelService.getModelInstance(context.getKnowledge().getVectorEmbedModelId());
            if (model == null) {
                return Result.fail(3, "知识库没有配置向量模型");
            }
            documentStore.setEmbeddingModel(model.toEmbeddingModel());
            StoreOptions options = StoreOptions.ofCollectionName(context.getKnowledge().getVectorStoreCollection());
            documentStore.delete(Collections.singletonList(chunkId), options);
            documentChunkService.removeById(chunkId);
            audit(context, "删除分享文档 Chunk", "KNOWLEDGE_SHARE_URL_WRITE", true,
                auditDetail("knowledgeId", context.getKnowledge().getId(), "chunkId", chunkId));
            return Result.ok(true);
        } finally {
            DocumentStoreLifecycleSupport.closeQuietly(documentStore);
        }
    }

    /**
     * FAQ 分类列表。
     */
    @GetMapping("/faqCategory/list")
    public Result<List<FaqCategory>> faqCategoryList(
        @RequestParam String shareKey,
        @RequestParam(required = false) Boolean asTree
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        return Result.ok(faqCategoryService.listByCollection(context.getKnowledge().getId(), asTree));
    }

    /**
     * 保存 FAQ 分类。
     */
    @PostMapping("/faqCategory/save")
    public Result<?> saveFaqCategory(
        @RequestParam String shareKey,
        @JsonBody FaqCategory entity
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            entity.getCollectionId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        audit(context, "新增 FAQ 分类", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", entity.getCollectionId()));
        return Result.ok(faqCategoryService.saveCategory(entity));
    }

    /**
     * 更新 FAQ 分类。
     */
    @PostMapping("/faqCategory/update")
    public Result<?> updateFaqCategory(
        @RequestParam String shareKey,
        @JsonBody FaqCategory entity
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_UPDATE.name()
        );
        audit(context, "更新 FAQ 分类", "KNOWLEDGE_SHARE_URL_WRITE", true,
            auditDetail("knowledgeId", context.getKnowledge().getId(), "categoryId", entity.getId()));
        return Result.ok(faqCategoryService.updateCategory(entity));
    }

    /**
     * 删除 FAQ 分类。
     */
    @PostMapping("/faqCategory/remove")
    public Result<?> removeFaqCategory(
        @RequestParam String shareKey,
        @JsonBody("id") BigInteger id
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_DELETE.name()
        );
        audit(context, "删除 FAQ 分类", "KNOWLEDGE_SHARE_URL_WRITE", true,
            auditDetail("knowledgeId", context.getKnowledge().getId(), "categoryId", id));
        return Result.ok(faqCategoryService.removeCategory(id));
    }

    /**
     * FAQ 分页。
     */
    @GetMapping("/faqItem/page")
    public Result<Page<FaqItem>> faqPage(
        @RequestParam String shareKey,
        HttpServletRequest request,
        @RequestParam(defaultValue = "1") long pageNumber,
        @RequestParam(defaultValue = "10") long pageSize
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        faqCategoryService.ensureDefaultCategory(context.getKnowledge().getId());
        QueryWrapper queryWrapper = QueryWrapper.create()
            .eq(FaqItem::getCollectionId, context.getKnowledge().getId());
        String question = request.getParameter("question");
        if (StringUtils.hasText(question)) {
            queryWrapper.like(FaqItem::getQuestion, question.trim());
        }
        String categoryId = request.getParameter("categoryId");
        if (StringUtils.hasText(categoryId)) {
            List<BigInteger> descendantIds =
                faqCategoryService.findDescendantIds(context.getKnowledge().getId(), 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(context.getKnowledge().getId());
        if (page.getRecords() != null) {
            for (FaqItem record : page.getRecords()) {
                record.setCategoryPath(pathMap.get(record.getCategoryId()));
            }
        }
        return Result.ok(page);
    }

    /**
     * FAQ 详情。
     */
    @GetMapping("/faqItem/detail")
    public Result<FaqItem> faqDetail(
        @RequestParam String shareKey,
        @RequestParam String id
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.VIEW.name()
        );
        FaqItem item = faqItemService.getById(id);
        if (item == null || item.getCollectionId() == null
            || item.getCollectionId().compareTo(context.getKnowledge().getId()) != 0) {
            throw new BusinessException("FAQ不存在");
        }
        return Result.ok(item);
    }

    /**
     * 保存 FAQ。
     */
    @PostMapping("/faqItem/save")
    public Result<?> saveFaq(
        @RequestParam String shareKey,
        @JsonBody FaqItem entity
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            entity.getCollectionId(),
            KnowledgeShareActionScope.CONTENT_CREATE.name()
        );
        audit(context, "新增 FAQ", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", entity.getCollectionId()));
        return Result.ok(faqItemService.saveFaqItem(entity));
    }

    /**
     * 更新 FAQ。
     */
    @PostMapping("/faqItem/update")
    public Result<?> updateFaq(
        @RequestParam String shareKey,
        @JsonBody FaqItem entity
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_UPDATE.name()
        );
        audit(context, "更新 FAQ", "KNOWLEDGE_SHARE_URL_WRITE", true,
            auditDetail("knowledgeId", context.getKnowledge().getId(), "faqId", entity.getId()));
        return Result.ok(faqItemService.updateFaqItem(entity));
    }

    /**
     * 删除 FAQ。
     */
    @PostMapping("/faqItem/remove")
    public Result<?> removeFaq(
        @RequestParam String shareKey,
        @JsonBody("id") BigInteger id
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            null,
            KnowledgeShareActionScope.CONTENT_DELETE.name()
        );
        audit(context, "删除 FAQ", "KNOWLEDGE_SHARE_URL_WRITE", true,
            auditDetail("knowledgeId", context.getKnowledge().getId(), "faqId", id));
        return Result.ok(faqItemService.removeFaqItem(id));
    }

    /**
     * 上传 FAQ 图片。
     */
    @PostMapping(value = "/faqItem/uploadImage", produces = MediaType.APPLICATION_JSON_VALUE)
    public Result<UploadResVo> uploadFaqImage(
        @RequestParam String shareKey,
        MultipartFile file,
        BigInteger collectionId
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            collectionId,
            KnowledgeShareActionScope.CONTENT_UPDATE.name()
        );
        if (file == null || file.isEmpty()) {
            throw new BusinessException("图片不能为空");
        }
        if (file.getSize() > MAX_IMAGE_SIZE_BYTES) {
            throw new BusinessException("图片大小不能超过5MB");
        }
        if (!isAllowedImageType(file)) {
            throw new BusinessException("仅支持 JPG/PNG/WEBP/GIF 图片");
        }
        String path = fileStorageService.save(file, "faq/" + collectionId);
        UploadResVo result = new UploadResVo();
        result.setPath(path);
        audit(context, "上传 FAQ 图片", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", collectionId));
        return Result.ok(result);
    }

    /**
     * 导入 FAQ Excel。
     */
    @PostMapping(value = "/faqItem/importExcel", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    public Result<FaqImportResultVo> importFaqExcel(
        @RequestParam String shareKey,
        MultipartFile file,
        BigInteger collectionId
    ) {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            collectionId,
            KnowledgeShareActionScope.IMPORT_EXPORT.name()
        );
        audit(context, "导入 FAQ Excel", "KNOWLEDGE_SHARE_URL_WRITE", true, auditDetail("knowledgeId", collectionId));
        return Result.ok(faqItemService.importFromExcel(collectionId, file));
    }

    /**
     * 下载 FAQ 导入模板。
     */
    @GetMapping("/faqItem/downloadImportTemplate")
    public void downloadFaqImportTemplate(
        @RequestParam String shareKey,
        @RequestParam BigInteger collectionId,
        HttpServletResponse response
    ) throws Exception {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            collectionId,
            KnowledgeShareActionScope.IMPORT_EXPORT.name()
        );
        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(context, "下载 FAQ 导入模板", "KNOWLEDGE_SHARE_URL_ACCESS", false, auditDetail("knowledgeId", collectionId));
    }

    /**
     * 导出 FAQ Excel。
     */
    @GetMapping("/faqItem/exportExcel")
    public void exportFaqExcel(
        @RequestParam String shareKey,
        @RequestParam BigInteger collectionId,
        HttpServletResponse response
    ) throws Exception {
        KnowledgeShareAuthContext context = knowledgeShareService.assertUrlShareAccess(
            shareKey,
            collectionId,
            KnowledgeShareActionScope.IMPORT_EXPORT.name()
        );
        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(collectionId, response.getOutputStream());
        response.flushBuffer();
        audit(context, "导出 FAQ Excel", "KNOWLEDGE_SHARE_URL_ACCESS", false, auditDetail("knowledgeId", collectionId));
    }

    private boolean isAllowedImageType(MultipartFile file) {
        String contentType = file.getContentType();
        return contentType != null && ALLOWED_IMAGE_TYPES.contains(contentType.toLowerCase());
    }

    /**
     * 校验分享请求中的知识库标识。
     *
     * @param knowledgeId 知识库 ID
     * @return 已校验的知识库 ID
     */
    private BigInteger resolveKnowledgeId(KnowledgeShareAuthContext context, BigInteger requestedKnowledgeId) {
        if (requestedKnowledgeId != null) {
            return requestedKnowledgeId;
        }
        if (context != null && context.getKnowledge() != null && context.getKnowledge().getId() != null) {
            return context.getKnowledge().getId();
        }
        if (context == null || context.getKnowledge() == null) {
            throw new BusinessException("知识库不能为空");
        }
        throw new BusinessException("知识库不存在");
    }

    /**
     * 构造允许空值的审计详情。
     *
     * @param keyValues 键值对
     * @return 审计详情
     */
    private Map<String, Object> auditDetail(Object... keyValues) {
        if (keyValues == null || keyValues.length == 0) {
            return new HashMap<>();
        }
        if ((keyValues.length & 1) != 0) {
            throw new IllegalArgumentException("审计详情参数必须成对出现");
        }
        Map<String, Object> detail = new HashMap<>(keyValues.length / 2);
        for (int index = 0; index < keyValues.length; index += 2) {
            Object key = keyValues[index];
            if (!(key instanceof String detailKey) || !StringUtils.hasText(detailKey)) {
                throw new IllegalArgumentException("审计详情 key 必须为非空字符串");
            }
            detail.put(detailKey, keyValues[index + 1]);
        }
        return detail;
    }

    /**
     * 记录分享访问审计。
     *
     * @param context 分享鉴权上下文
     * @param actionName 动作名称
     * @param actionType 动作类型
     * @param writeOperation 是否写操作
     * @param detail 审计详情
     */
    private void audit(KnowledgeShareAuthContext context, String actionName, String actionType, boolean writeOperation, Map<String, Object> detail) {
        Map<String, Object> payload = new HashMap<>(detail);
        payload.put("shareId", context.getShare().getId());
        payload.put("writeOperation", writeOperation);
        knowledgeShareAuditService.log(null, actionName, actionType, "/api/v1/share/knowledge", payload);
    }

    private List<KnowledgeSearchResultItem> toKnowledgeSearchResult(List<com.easyagents.core.document.Document> documents) {
        List<KnowledgeSearchResultItem> result = new java.util.ArrayList<>();
        if (documents == null) {
            return result;
        }
        for (int index = 0; index < documents.size(); index++) {
            com.easyagents.core.document.Document document = documents.get(index);
            if (document == null) {
                continue;
            }
            KnowledgeSearchResultItem item = new KnowledgeSearchResultItem();
            item.setSorting(index + 1);
            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() == null ? null : document.getScore().doubleValue());
            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();
        }
        if (value instanceof String text && StringUtils.hasText(text)) {
            try {
                return Double.parseDouble(text);
            } catch (NumberFormatException ignore) {
                return null;
            }
        }
        return null;
    }
}
