package com.yanzhu.module.ai.service.knowledge;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;

import com.yanzhu.framework.common.enums.CommonStatusEnum;
import com.yanzhu.framework.common.pojo.PageResult;
import com.yanzhu.framework.common.util.object.BeanUtils;
import com.yanzhu.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentPageReqVO;
import com.yanzhu.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentProcessRespVO;
import com.yanzhu.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentSaveReqVO;
import com.yanzhu.module.ai.controller.admin.knowledge.vo.segment.AiKnowledgeSegmentUpdateStatusReqVO;
import com.yanzhu.module.ai.dal.dataobject.knowledge.AiKnowledgeDO;
import com.yanzhu.module.ai.dal.dataobject.knowledge.AiKnowledgeDocumentDO;
import com.yanzhu.module.ai.dal.dataobject.knowledge.AiKnowledgeSegmentDO;
import com.yanzhu.module.ai.dal.mysql.knowledge.AiKnowledgeSegmentMapper;
import com.yanzhu.module.ai.enums.AiDocumentSplitStrategyEnum;
import com.yanzhu.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchReqBO;
import com.yanzhu.module.ai.service.knowledge.bo.AiKnowledgeSegmentSearchRespBO;
import com.yanzhu.module.ai.framework.ai.compat.TextSplitter;
import com.yanzhu.module.ai.framework.ai.compat.TokenTextSplitter;
import com.yanzhu.module.ai.service.knowledge.splitter.MarkdownQaSplitter;
import com.yanzhu.module.ai.service.knowledge.splitter.SemanticTextSplitter;
import com.yanzhu.module.ai.service.model.AiModelService;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.data.embedding.Embedding;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.util.*;

import static com.yanzhu.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yanzhu.framework.common.util.collection.CollectionUtils.convertList;
import static com.yanzhu.module.ai.enums.ErrorCodeConstants.*;

/**
 * AI 知识库分片 Service 实现类
 *
 * @author xiaoxin
 */
@Service
@Slf4j
public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService {

    private static final String VECTOR_STORE_METADATA_KNOWLEDGE_ID = "knowledgeId";
    private static final String VECTOR_STORE_METADATA_DOCUMENT_ID = "documentId";
    private static final String VECTOR_STORE_METADATA_SEGMENT_ID = "segmentId";

    private static final Map<String, Class<?>> VECTOR_STORE_METADATA_TYPES;
    static {
        Map<String, Class<?>> map = new HashMap<>();
        map.put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, String.class);
        map.put(VECTOR_STORE_METADATA_DOCUMENT_ID, String.class);
        map.put(VECTOR_STORE_METADATA_SEGMENT_ID, String.class);
        VECTOR_STORE_METADATA_TYPES = Collections.unmodifiableMap(map);
    }

    /**
     * Rerank 在向量检索时，检索数量 * 该系数，目的是为了提升 Rerank 的效果
     */
    private static final Integer RERANK_RETRIEVAL_FACTOR = 4;

    @Resource
    private AiKnowledgeSegmentMapper segmentMapper;

    /**
     * 估算文本的token数量（简单估算方法）
     */
    private Integer estimateTokens(String text) {
        if (text == null || text.isEmpty()) {
            return 0;
        }
        // 简单的token估算：按字符数除以4来估算（针对中文场景）
        return Math.max(1, text.length() / 4);
    }

    @Resource
    private AiKnowledgeService knowledgeService;
    @Resource
    @Lazy // 延迟加载，避免循环依赖
    private AiKnowledgeDocumentService knowledgeDocumentService;
    @Resource
    private AiModelService modelService;





    @Override
    public PageResult<AiKnowledgeSegmentDO> getKnowledgeSegmentPage(AiKnowledgeSegmentPageReqVO pageReqVO) {
        return segmentMapper.selectPage(pageReqVO);
    }

    @Override
    public void createKnowledgeSegmentBySplitContent(Long documentId, String content) {
        // 1. 校验
        AiKnowledgeDocumentDO documentDO = knowledgeDocumentService.validateKnowledgeDocumentExists(documentId);
        AiKnowledgeDO knowledgeDO = knowledgeService.validateKnowledgeExists(documentDO.getKnowledgeId());
        EmbeddingStore<TextSegment> embeddingStore = getVectorStoreById(knowledgeDO);

        // 2. 文档切片（使用自动检测策略）
        List<Document> documentSegments = splitContentByStrategy(content, documentDO.getSegmentMaxTokens(),
                AiDocumentSplitStrategyEnum.AUTO, documentDO.getUrl());

        // 3.1 存储切片
        List<AiKnowledgeSegmentDO> segmentDOs = convertList(documentSegments, segment -> {
            if (StrUtil.isEmpty(segment.text())) {
                return null;
            }
            return new AiKnowledgeSegmentDO().setKnowledgeId(documentDO.getKnowledgeId()).setDocumentId(documentId)
                    .setContent(segment.text()).setContentLength(segment.text().length())
                    .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY)
                    .setTokens(estimateTokens(segment.text()))
                    .setStatus(CommonStatusEnum.ENABLE.getStatus());
        });
        segmentMapper.insertBatch(segmentDOs);
        // 3.2 切片向量化
        for (int i = 0; i < documentSegments.size(); i++) {
            Document segment = documentSegments.get(i);
            AiKnowledgeSegmentDO segmentDO = segmentDOs.get(i);
            writeVectorStore(embeddingStore, segmentDO, segment);
        }
    }

    @Override
    public void updateKnowledgeSegment(AiKnowledgeSegmentSaveReqVO reqVO) {
        // 1. 校验
        AiKnowledgeSegmentDO oldSegment = validateKnowledgeSegmentExists(reqVO.getId());

        // 2. 删除向量
        // 暂时注释掉向量操作代码，因为VectorStore来自springframework.ai
        // EmbeddingStore<TextSegment> embeddingStore = getVectorStoreById(oldSegment.getKnowledgeId());
        // deleteVectorStore(embeddingStore, oldSegment);

        // 3.1 更新切片
        AiKnowledgeSegmentDO newSegment = BeanUtils.toBean(reqVO, AiKnowledgeSegmentDO.class);
        segmentMapper.updateById(newSegment);
        // 3.2 重新向量化，必须开启状态
        if (CommonStatusEnum.isEnable(oldSegment.getStatus())) {
            newSegment.setKnowledgeId(oldSegment.getKnowledgeId()).setDocumentId(oldSegment.getDocumentId());
            // 暂时注释掉向量化代码，因为VectorStore来自springframework.ai
            // writeVectorStore(vectorStore, newSegment, new Document(newSegment.getContent()));
        }
    }

    @Override
    public void deleteKnowledgeSegmentByDocumentId(Long documentId) {
        // 1. 查询需要删除的段落
        List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByDocumentId(documentId);
        if (CollUtil.isEmpty(segments)) {
            return;
        }

        // 2. 批量删除段落记录
        segmentMapper.deleteByIds(convertList(segments, AiKnowledgeSegmentDO::getId));

        // 3. 删除向量存储中的段落
        // 暂时注释掉向量操作代码，因为VectorStore来自springframework.ai
        // EmbeddingStore<TextSegment> embeddingStore = getVectorStoreById(segments.get(0).getKnowledgeId());
        // for (AiKnowledgeSegmentDO segment : segments) {
        //     deleteVectorStore(embeddingStore, segment);
        // }
    }

    @Override
    public void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO) {
        // 1. 校验
        AiKnowledgeSegmentDO segment = validateKnowledgeSegmentExists(reqVO.getId());

        // 2. 更新状态
        segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(reqVO.getId()).setStatus(reqVO.getStatus()));

        // 3. 更新向量
        // 暂时注释掉向量操作代码，因为VectorStore来自springframework.ai
        // if (CommonStatusEnum.isEnable(reqVO.getStatus())) {
        //     EmbeddingStore<TextSegment> embeddingStore = getVectorStoreById(segment.getKnowledgeId());
        //     writeVectorStore(embeddingStore, segment, new Document(segment.getContent()));
        // } else {
        //     EmbeddingStore<TextSegment> embeddingStore = getVectorStoreById(segment.getKnowledgeId());
        //     deleteVectorStore(embeddingStore, segment);
        // }
    }

    @Override
    public void reindexKnowledgeSegmentByKnowledgeId(Long knowledgeId) {
        // 1.1 校验知识库存在
        AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
        // 1.2 获取知识库向量实例
        // 暂时注释掉向量操作代码，因为VectorStore来自springframework.ai
        // EmbeddingStore<TextSegment> embeddingStore = getVectorStoreById(knowledge);

        // 2.1 查询知识库下的所有启用状态的段落
        List<AiKnowledgeSegmentDO> segments = segmentMapper.selectListByKnowledgeIdAndStatus(
                knowledgeId, CommonStatusEnum.ENABLE.getStatus());
        if (CollUtil.isEmpty(segments)) {
            return;
        }
        // 2.2 遍历所有段落，重新索引
        // 暂时注释掉向量操作代码，因为VectorStore来自springframework.ai
        // for (AiKnowledgeSegmentDO segment : segments) {
        //     // 删除旧的向量
        //     deleteVectorStore(embeddingStore, segment);
        //     // 重新创建向量
        //     writeVectorStore(embeddingStore, segment, new Document(segment.getContent()));
        // }
        log.info("[reindexKnowledgeSegmentByKnowledgeId][知识库({}) 重新索引完成，共处理 {} 个段落]",
                knowledgeId, segments.size());
    }

    private void writeVectorStore(EmbeddingStore<TextSegment> embeddingStore, AiKnowledgeSegmentDO segmentDO, Document segment) {
        // 1. 向量存储
        // 暂时注释掉向量操作代码，因为需要重新实现langchain4j的向量存储逻辑
        // 创建TextSegment并添加元数据
        // Metadata metadata = new Metadata();
        // metadata.put(VECTOR_STORE_METADATA_KNOWLEDGE_ID, segmentDO.getKnowledgeId().toString());
        // metadata.put(VECTOR_STORE_METADATA_DOCUMENT_ID, segmentDO.getDocumentId().toString());
        // metadata.put(VECTOR_STORE_METADATA_SEGMENT_ID, segmentDO.getId().toString());
        
        // TextSegment textSegment = TextSegment.from(segment.text(), metadata);
        // String id = embeddingStore.add(textSegment);

        // 2. 更新向量 ID
        // segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId()).setVectorId(id));
    }

    private void deleteVectorStore(EmbeddingStore<TextSegment> embeddingStore, AiKnowledgeSegmentDO segmentDO) {
        // 1. 更新向量 ID
        if (StrUtil.isEmpty(segmentDO.getVectorId())) {
            return;
        }
        segmentMapper.updateById(new AiKnowledgeSegmentDO().setId(segmentDO.getId())
                .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY));

        // 2. 删除向量
        // 暂时注释掉向量操作代码，因为需要重新实现langchain4j的向量删除逻辑
        // embeddingStore.remove(segmentDO.getVectorId());
    }

    @Override
    public List<AiKnowledgeSegmentSearchRespBO> searchKnowledgeSegment(AiKnowledgeSegmentSearchReqBO reqBO) {
        // 1. 校验
        AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(reqBO.getKnowledgeId());

        // 2. 检索
        List<Document> documents = searchDocument(knowledge, reqBO);
        if (CollUtil.isEmpty(documents)) {
            return ListUtil.empty();
        }

        // 3.1 段落召回
        // 暂时返回空列表，因为需要重新实现向量检索逻辑
        // 后续需要使用langchain4j的EmbeddingStore API来实现
        return ListUtil.empty();
    }

    /**
     * 基于 Embedding + Rerank Model，检索知识库中的文档
     *
     * @param knowledge 知识库
     * @param reqBO 检索请求
     * @return 文档列表
     */
    private List<Document> searchDocument(AiKnowledgeDO knowledge, AiKnowledgeSegmentSearchReqBO reqBO) {
        // 暂时返回空列表，因为需要重新实现向量检索逻辑
        // 后续需要使用langchain4j的EmbeddingStore API来实现
        return Collections.emptyList();
    }

    @Override
    public List<AiKnowledgeSegmentDO> splitContent(String url, Integer segmentMaxTokens) {
        // 1. 读取 URL 内容
        String content = knowledgeDocumentService.readUrl(url);

        // 2.1 自动检测文档类型并选择策略
        AiDocumentSplitStrategyEnum strategy = detectDocumentStrategy(content, url);
        // 2.2 文档切片
        List<Document> documentSegments = splitContentByStrategy(content, segmentMaxTokens, strategy, url);

        // 3. 转换为段落对象
        return convertList(documentSegments, segment -> {
            if (StrUtil.isEmpty(segment.text())) {
                return null;
            }
            return new AiKnowledgeSegmentDO()
                    .setContent(segment.text())
                    .setContentLength(segment.text().length())
                    .setTokens(estimateTokens(segment.text()));
        });
    }

    /**
     * 校验段落是否存在
     *
     * @param id 文档编号
     * @return 段落信息
     */
    private AiKnowledgeSegmentDO validateKnowledgeSegmentExists(Long id) {
        AiKnowledgeSegmentDO knowledgeSegment = segmentMapper.selectById(id);
        if (knowledgeSegment == null) {
            throw exception(KNOWLEDGE_SEGMENT_NOT_EXISTS);
        }
        return knowledgeSegment;
    }

    private EmbeddingStore<TextSegment> getVectorStoreById(AiKnowledgeDO knowledge) {
        return modelService.getOrCreateVectorStore(knowledge.getEmbeddingModelId(), VECTOR_STORE_METADATA_TYPES);
    }

    private EmbeddingStore<TextSegment> getVectorStoreById(Long knowledgeId) {
        AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(knowledgeId);
        return getVectorStoreById(knowledge);
    }

    /**
     * 根据策略切分内容
     *
     * @param content 文档内容
     * @param segmentMaxTokens 分段的最大 Token 数
     * @param strategy 切片策略
     * @param url 文档 URL（用于自动检测文件类型）
     * @return 切片后的文档列表
     */
    @SuppressWarnings("EnhancedSwitchMigration")
    private List<Document> splitContentByStrategy(String content, Integer segmentMaxTokens,
                                                  AiDocumentSplitStrategyEnum strategy, String url) {
        // 自动检测策略
        if (strategy == AiDocumentSplitStrategyEnum.AUTO) {
            strategy = detectDocumentStrategy(content, url);
            log.info("[splitContentByStrategy][自动检测到文档策略: {}]", strategy.getName());
        }
        // 根据策略切分
        TextSplitter textSplitter;
        switch (strategy) {
            case MARKDOWN_QA:
                textSplitter = new MarkdownQaSplitter(segmentMaxTokens);
                break;
            case SEMANTIC:
                textSplitter = new SemanticTextSplitter(segmentMaxTokens);
                break;
            case PARAGRAPH:
                textSplitter = new SemanticTextSplitter(segmentMaxTokens, 0); // 段落切分，无重叠
                break;
            case TOKEN:
            default:
                textSplitter = buildTokenTextSplitter(segmentMaxTokens);
                break;
        }
        // 执行切分
        return textSplitter.apply(Collections.singletonList(new Document(content)));
    }

    /**
     * 自动检测文档类型并选择切片策略
     *
     * @param content 文档内容
     * @param url 文档 URL
     * @return 推荐的切片策略
     */
    private AiDocumentSplitStrategyEnum detectDocumentStrategy(String content, String url) {
        if (StrUtil.isEmpty(content)) {
            return AiDocumentSplitStrategyEnum.TOKEN;
        }
        // 1. 检测 Markdown QA 格式
        if (isMarkdownQaFormat(content, url)) {
            return AiDocumentSplitStrategyEnum.MARKDOWN_QA;
        }
        // 2. 检测普通 Markdown 文档
        if (isMarkdownDocument(url)) {
            return AiDocumentSplitStrategyEnum.SEMANTIC;
        }
        // 3. 默认使用语义切分（比 Token 切分更智能）
        return AiDocumentSplitStrategyEnum.SEMANTIC;
    }

    /**
     * 检测是否为 Markdown QA 格式
     * 特征：包含多个二级标题（## ）且标题后紧跟答案内容
     */
    private boolean isMarkdownQaFormat(String content, String url) {
        // 文件扩展名判断
        if (StrUtil.isNotEmpty(url) && !url.toLowerCase().endsWith(".md")) {
            return false;
        }

        // 统计二级标题数量
        long h2Count = 0;
        long totalLines = 0;
        for (String line : content.split("\\r?\\n")) {
            totalLines++;
            if (line.trim().startsWith("## ")) {
                h2Count++;
            }
        }

        // 要求一：至少包含 2 个二级标题才认为是 QA 格式
        if (h2Count < 2) {
            return false;
        }

        // 要求二：检查标题占比（QA 文档标题行数相对较多），如果二级标题占比超过 10%，认为是 QA 格式
        double h2Ratio = (double) h2Count / totalLines;
        return h2Ratio > 0.1;
    }

    /**
     * 检测是否为 Markdown 文档
     */
    private boolean isMarkdownDocument(String url) {
        return StrUtil.endWithAnyIgnoreCase(url, ".md", ".markdown");
    }

    /**
     * 构建基于 Token 的文本切片器（原有逻辑保留）
     */
    private static TextSplitter buildTokenTextSplitter(Integer segmentMaxTokens) {
        return TokenTextSplitter.builder()
                .withChunkSize(segmentMaxTokens)
                .withMinChunkSizeChars(Integer.MAX_VALUE) // 忽略字符的截断
                .withMinChunkLengthToEmbed(1) // 允许的最小有效分段长度
                .withMaxNumChunks(Integer.MAX_VALUE)
                .withKeepSeparator(true) // 保留分隔符
                .build();
    }

    @Override
    public List<AiKnowledgeSegmentProcessRespVO> getKnowledgeSegmentProcessList(List<Long> documentIds) {
        if (CollUtil.isEmpty(documentIds)) {
            return Collections.emptyList();
        }
        return segmentMapper.selectProcessList(documentIds);
    }

    @Override
    public Long createKnowledgeSegment(AiKnowledgeSegmentSaveReqVO createReqVO) {
        // 1.1 校验文档是否存在
        AiKnowledgeDocumentDO document = knowledgeDocumentService
                .validateKnowledgeDocumentExists(createReqVO.getDocumentId());
        // 1.2 获取知识库信息
        AiKnowledgeDO knowledge = knowledgeService.validateKnowledgeExists(document.getKnowledgeId());
        // 1.3 校验 token 熟练
        Integer tokens = estimateTokens(createReqVO.getContent());
        if (tokens > document.getSegmentMaxTokens()) {
            throw exception(KNOWLEDGE_SEGMENT_CONTENT_TOO_LONG, tokens, document.getSegmentMaxTokens());
        }

        // 2. 保存段落
        AiKnowledgeSegmentDO segment = BeanUtils.toBean(createReqVO, AiKnowledgeSegmentDO.class)
                .setKnowledgeId(knowledge.getId()).setDocumentId(document.getId())
                .setContentLength(createReqVO.getContent().length()).setTokens(tokens)
                .setVectorId(AiKnowledgeSegmentDO.VECTOR_ID_EMPTY)
                .setRetrievalCount(0).setStatus(CommonStatusEnum.ENABLE.getStatus());
        segmentMapper.insert(segment);

        // 3. 向量化
        // 暂时注释掉向量操作代码，因为需要重新实现langchain4j的向量存储逻辑
        // writeVectorStore(getVectorStoreById(knowledge), segment, new Document(segment.getContent()));
        return segment.getId();
    }

    @Override
    public AiKnowledgeSegmentDO getKnowledgeSegment(Long id) {
        return segmentMapper.selectById(id);
    }

    @Override
    public List<AiKnowledgeSegmentDO> getKnowledgeSegmentList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return segmentMapper.selectByIds(ids);
    }

}
