package com.emoon.knowledge.service.impl;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.emoon.common.core.utils.MapstructUtils;
import com.emoon.common.core.utils.StringUtils;
import com.emoon.common.mybatis.core.page.PageQuery;
import com.emoon.common.mybatis.core.page.TableDataInfo;
import com.emoon.common.vector.domain.bo.StoreEmbeddingBo;
import com.emoon.knowledge.chain.loader.ResourceLoader;
import com.emoon.knowledge.chain.loader.ResourceLoaderFactory;
import com.emoon.knowledge.domain.KnowledgeAttach;
import com.emoon.knowledge.domain.KnowledgeFragment;
import com.emoon.knowledge.domain.KnowledgeInfo;
import com.emoon.knowledge.domain.bo.KnowledgeAttachBo;
import com.emoon.knowledge.domain.bo.KnowledgeInfoUploadBo;
import com.emoon.knowledge.domain.vo.KnowledgeAttachVo;
import com.emoon.knowledge.domain.vo.KnowledgeInfoVo;
import com.emoon.knowledge.mapper.KnowledgeAttachMapper;
import com.emoon.knowledge.mapper.KnowledgeFragmentMapper;
import com.emoon.knowledge.mapper.KnowledgeInfoMapper;
import com.emoon.knowledge.service.IKnowledgeAttachService;
import com.emoon.knowledge.service.VectorStoreService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 【请填写功能名称】Service业务层处理
 *
 * @author wangkang
 * @date 2025-11-25
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class KnowledgeAttachServiceImpl implements IKnowledgeAttachService {

    private final KnowledgeAttachMapper baseMapper;
    private final KnowledgeFragmentMapper fragmentMapper;
    private final KnowledgeInfoMapper knowledgeInfoMapper;
    private final ResourceLoaderFactory resourceLoaderFactory;
    private final VectorStoreService vectorStoreService;

    @Value("${file.upload-path}")
    private String uploadPath;

    @Value("${file.base-url}")
    private String baseUrl;

    @Value("${embedding.base-url:}")
    private String embeddingBaseUrl;

    /**
     * 查询【请填写功能名称】
     *
     * @param id 主键
     * @return 【请填写功能名称】
     */
    @Override
    public KnowledgeAttachVo queryById(Long id){
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询【请填写功能名称】列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 【请填写功能名称】分页列表
     */
    @Override
    public TableDataInfo<KnowledgeAttachVo> queryPageList(KnowledgeAttachBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<KnowledgeAttach> lqw = buildQueryWrapper(bo);
        Page<KnowledgeAttachVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的【请填写功能名称】列表
     *
     * @param bo 查询条件
     * @return 【请填写功能名称】列表
     */
    @Override
    public List<KnowledgeAttachVo> queryList(KnowledgeAttachBo bo) {
        LambdaQueryWrapper<KnowledgeAttach> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<KnowledgeAttach> buildQueryWrapper(KnowledgeAttachBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<KnowledgeAttach> lqw = Wrappers.lambdaQuery();
        lqw.orderByAsc(KnowledgeAttach::getId);
        lqw.eq(StringUtils.isNotBlank(bo.getKid()), KnowledgeAttach::getKid, bo.getKid());
        lqw.eq(StringUtils.isNotBlank(bo.getDocId()), KnowledgeAttach::getDocId, bo.getDocId());
        lqw.like(StringUtils.isNotBlank(bo.getDocName()), KnowledgeAttach::getDocName, bo.getDocName());
        lqw.eq(StringUtils.isNotBlank(bo.getDocType()), KnowledgeAttach::getDocType, bo.getDocType());
        lqw.eq(bo.getOssId() != null, KnowledgeAttach::getOssId, bo.getOssId());
        lqw.eq(bo.getPicStatus() != null, KnowledgeAttach::getPicStatus, bo.getPicStatus());
        lqw.eq(bo.getPicAnysStatus() != null, KnowledgeAttach::getPicAnysStatus, bo.getPicAnysStatus());
        lqw.eq(bo.getVectorStatus() != null, KnowledgeAttach::getVectorStatus, bo.getVectorStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getContent()), KnowledgeAttach::getContent, bo.getContent());
        lqw.eq(StringUtils.isNotBlank(bo.getFilePath()), KnowledgeAttach::getFilePath, bo.getFilePath());
        lqw.eq(StringUtils.isNotBlank(bo.getPublicUrl()), KnowledgeAttach::getPublicUrl, bo.getPublicUrl());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), KnowledgeAttach::getStatus, bo.getStatus());
        lqw.eq(StringUtils.isNotBlank(bo.getDefFlag()), KnowledgeAttach::getDefFlag, bo.getDefFlag());
        return lqw;
    }

    /**
     * 新增【请填写功能名称】
     *
     * @param bo 【请填写功能名称】
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(KnowledgeAttachBo bo) {
        KnowledgeAttach add = MapstructUtils.convert(bo, KnowledgeAttach.class);
        validEntityBeforeSave(add);
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改【请填写功能名称】
     *
     * @param bo 【请填写功能名称】
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(KnowledgeAttachBo bo) {
        KnowledgeAttach update = MapstructUtils.convert(bo, KnowledgeAttach.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(KnowledgeAttach entity){
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除【请填写功能名称】信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    /**
     * 上传知识库附件
     *
     * @param bo 上传业务对象
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void upload(KnowledgeInfoUploadBo bo) throws Exception {
        storeContent(bo.getFile(), bo.getKid());
    }

    /**
     * 存储文件内容
     *
     * @param file 上传的文件
     * @param kid  知识库ID
     */
    private void storeContent(MultipartFile file, String kid) {
        String fileName = file.getOriginalFilename();
        List<String> chunkList = new ArrayList<>();
        KnowledgeAttach knowledgeAttach = new KnowledgeAttach();
        
        // 前端传入的kid实际是id，存储片段时直接使用这个id值作为kid
        // 但查询知识库信息时需要通过id查询，获取真正的kid值用于向量存储
        KnowledgeInfoVo knowledgeInfoVo = null;
        String actualKidForVector = null;  // 用于向量存储的真正kid值
        try {
            Long knowledgeId = Long.valueOf(kid);
            knowledgeInfoVo = knowledgeInfoMapper.selectVoById(knowledgeId);
            if (knowledgeInfoVo != null) {
                actualKidForVector = knowledgeInfoVo.getKid();  // 获取真正的kid值用于向量存储
            }
        } catch (NumberFormatException e) {
            // 如果不是数字，则通过kid查询
            knowledgeInfoVo = knowledgeInfoMapper.selectVoOne(Wrappers.<KnowledgeInfo>lambdaQuery()
                    .eq(KnowledgeInfo::getKid, kid));
            if (knowledgeInfoVo != null) {
                actualKidForVector = kid;  // 如果本身就是kid，则直接使用
            }
        }
        
        // 存储片段时使用传入的kid值（实际是id）
        knowledgeAttach.setKid(kid);
        String docId = RandomUtil.randomString(10);
        try {
            // 根据 kid 生成子目录
            Path targetParentPath = Paths.get(uploadPath, kid);
            Files.createDirectories(targetParentPath); // 确保目录存在

            // 文件存储路径为 uploadPath/kid/fileName
            Path targetPath = targetParentPath.resolve(fileName);

            file.transferTo(targetPath);

            // 生成可访问的 HTTP 链接
            String downloadUrl = baseUrl + "/api/file/view?docId=" + URLEncoder.encode(docId, StandardCharsets.UTF_8);

            // 保存信息
            knowledgeAttach.setDocId(docId);
            knowledgeAttach.setDocName(fileName);
            knowledgeAttach.setDocType(fileName.substring(fileName.lastIndexOf(".") + 1));
            knowledgeAttach.setPublicUrl(downloadUrl);// HTTP 下载链接
            knowledgeAttach.setFilePath(targetPath.toString());

        } catch (IOException e) {
            log.error("保存文件失败", e);
            throw new RuntimeException("文件保存失败", e);
        }

        String content = "";
        ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(knowledgeAttach.getDocType());
        List<String> fids = new ArrayList<>();
        try {
            content = resourceLoader.getContent(file.getInputStream());
            // 文本分割时传入kid（实际是id），CharacterTextSplitter会通过id查询知识库配置
            chunkList = resourceLoader.getChunkList(content, kid);
            List<KnowledgeFragment> knowledgeFragmentList = new ArrayList<>();
            if (CollUtil.isNotEmpty(chunkList)) {
                for (int i = 0; i < chunkList.size(); i++) {
                    String fid = RandomUtil.randomString(10);
                    fids.add(fid);
                    KnowledgeFragment knowledgeFragment = new KnowledgeFragment();
                    // 存储片段时使用传入的kid值（实际是id）
                    knowledgeFragment.setKid(kid);
                    knowledgeFragment.setDocId(docId);
                    knowledgeFragment.setFid(fid);
                    knowledgeFragment.setIdx((long) i);
                    knowledgeFragment.setContent(chunkList.get(i));
                    knowledgeFragment.setCreateTime(new Date());
                    knowledgeFragmentList.add(knowledgeFragment);
                }
            }
            // 批量插入知识片段
            if (CollUtil.isNotEmpty(knowledgeFragmentList)) {
                for (KnowledgeFragment fragment : knowledgeFragmentList) {
                    fragmentMapper.insert(fragment);
                }
            }
        } catch (IOException e) {
            log.error("保存知识库信息失败！{}", e.getMessage());
        }
        knowledgeAttach.setContent(content);
        knowledgeAttach.setCreateTime(new Date());
        knowledgeAttach.setPicStatus(1L);  // 设置默认值
        knowledgeAttach.setPicAnysStatus(1L);  // 设置默认值
        baseMapper.insert(knowledgeAttach);

        // 使用之前查询到的知识库信息进行向量存储
        if (knowledgeInfoVo != null && actualKidForVector != null) {
            // 构建向量存储参数（从配置中读取模型地址等信息）
            StoreEmbeddingBo storeEmbeddingBo = StoreEmbeddingBo.builder()
                    .kid(actualKidForVector)  // 使用真正的kid值（用于向量存储的className）
                    .docId(docId)
                    .fids(fids)
                    .chunkList(chunkList)
                    .vectorModelName(knowledgeInfoVo.getVectorModelName())
                    .embeddingModelName(knowledgeInfoVo.getEmbeddingModelName())
                    .apiKey("")  // 无需 API Key
                    .baseUrl(embeddingBaseUrl)
                    .fileName(fileName)
                    .build();

            // 调用向量存储服务存储嵌入向量
            vectorStoreService.storeEmbeddings(storeEmbeddingBo);
            log.info("向量存储完成: {}", storeEmbeddingBo);
        } else {
            log.warn("未找到知识库信息，kid: {}, 跳过向量存储", kid);
        }
    }

    /**
     * 删除知识库附件（包括附件和片段）
     *
     * @param docId 文档ID
     */
    @Override
    public void removeKnowledgeAttach(String docId) {
        // 删除附件
        LambdaQueryWrapper<KnowledgeAttach> attachWrapper = Wrappers.lambdaQuery();
        attachWrapper.eq(KnowledgeAttach::getDocId, docId);
        baseMapper.delete(attachWrapper);

        // 删除片段
        LambdaQueryWrapper<KnowledgeFragment> fragmentWrapper = Wrappers.lambdaQuery();
        fragmentWrapper.eq(KnowledgeFragment::getDocId, docId);
        fragmentMapper.delete(fragmentWrapper);
    }
}
