package org.dromara.system.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 lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.system.ai.domain.bo.AiStoreEmbeddingBo;
import org.dromara.system.ai.loader.ResourceLoader;
import org.dromara.system.ai.loader.ResourceLoaderFactory;
import org.dromara.system.ai.service.VectorStoreService;
import org.dromara.system.domain.AiChatModelDo;
import org.dromara.system.domain.AiKnowledgeAttachDo;
import org.dromara.system.domain.AiKnowledgeFragmentDo;
import org.dromara.system.domain.AiKnowledgeInfoDo;
import org.dromara.system.domain.bo.AiKnowledgeAttachBo;
import org.dromara.system.domain.bo.AiKnowledgeAttachUploadBo;
import org.dromara.system.domain.vo.AiKnowledgeAttachVo;
import org.dromara.system.mapper.AiChatModelMapper;
import org.dromara.system.mapper.AiKnowledgeAttachMapper;
import org.dromara.system.mapper.AiKnowledgeFragmentMapper;
import org.dromara.system.mapper.AiKnowledgeInfoMapper;
import org.dromara.system.service.IAiKnowledgeAttachService;
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.List;
import java.util.Map;

/**
 * 知识库附件Service业务层处理
 *
 * @author Lion Li
 * @date 2025-09-17
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class AiKnowledgeAttachServiceImpl implements IAiKnowledgeAttachService {

    @Value("${file.upload-path:E:/knowledge/files/}")
    private String uploadPath;

    @Value("${file.base-url:http://8.137.127.56:5666}")
    private String baseUrl;

    private final AiKnowledgeInfoMapper knowledgeInfoMapper;

    private final AiKnowledgeAttachMapper knowledgeAttachMapper;

    private final AiKnowledgeFragmentMapper knowledgeFragmentMapper;

    private final ResourceLoaderFactory resourceLoaderFactory;

    private final AiChatModelMapper chatModelMapper;

    private final VectorStoreService vectorStoreService;

    /**
     * 查询知识库附件
     *
     * @param id 主键
     * @return 知识库附件
     */
    @Override
    public AiKnowledgeAttachVo queryById(Long id) {
        return knowledgeAttachMapper.selectVoById(id);
    }

    /**
     * 分页查询知识库附件列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 知识库附件分页列表
     */
    @Override
    public TableDataInfo<AiKnowledgeAttachVo> queryPageList(AiKnowledgeAttachBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<AiKnowledgeAttachDo> lqw = buildQueryWrapper(bo);
        Page<AiKnowledgeAttachVo> result = knowledgeAttachMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的知识库附件列表
     *
     * @param bo 查询条件
     * @return 知识库附件列表
     */
    @Override
    public List<AiKnowledgeAttachVo> queryList(AiKnowledgeAttachBo bo) {
        LambdaQueryWrapper<AiKnowledgeAttachDo> lqw = buildQueryWrapper(bo);
        return knowledgeAttachMapper.selectVoList(lqw);
    }

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

    /**
     * 新增知识库附件
     *
     * @param bo 知识库附件
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(AiKnowledgeAttachBo bo) {
        AiKnowledgeAttachDo add = MapstructUtils.convert(bo, AiKnowledgeAttachDo.class);
        validEntityBeforeSave(add);
        boolean flag = knowledgeAttachMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改知识库附件
     *
     * @param bo 知识库附件
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(AiKnowledgeAttachBo bo) {
        AiKnowledgeAttachDo update = MapstructUtils.convert(bo, AiKnowledgeAttachDo.class);
        validEntityBeforeSave(update);
        return knowledgeAttachMapper.updateById(update) > 0;
    }

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

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

    /**
     * 上传
     *
     * @param aiKnowledgeAttachUploadBo
     */
    /**
     * 上传
     *
     * @param aiKnowledgeAttachUploadBo
     */
    @Transactional
    @Override
    public void upload(AiKnowledgeAttachUploadBo aiKnowledgeAttachUploadBo) {
        MultipartFile file = aiKnowledgeAttachUploadBo.getFile();
        String knowledgeCode = aiKnowledgeAttachUploadBo.getKnowledgeCode();
        String docId = RandomUtil.randomString(10);

        // 生成文件目录
        Path targetParentPath = Paths.get(uploadPath, knowledgeCode);
        try {
            Files.createDirectories(targetParentPath);
        } catch (IOException e) {
            throw new RuntimeException("创建目录失败", e);
        }

        String fileName = file.getOriginalFilename();
        Path targetPath = targetParentPath.resolve(fileName);

        // 将文件写入磁盘
        try {
            file.transferTo(targetPath); // 核心修改点：将上传的文件保存到指定路径
        } catch (IOException e) {
            throw new RuntimeException("文件保存失败", e);
        }

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

        String fileType = fileName.substring(fileName.lastIndexOf(".") + 1);
        String content;
        List<String> chunkList;
        ResourceLoader resourceLoader = resourceLoaderFactory.getLoaderByFileType(fileType);
        try {
            content = resourceLoader.getContent(aiKnowledgeAttachUploadBo.getFile().getInputStream());
            chunkList = resourceLoader.getChunkList(content, aiKnowledgeAttachUploadBo.getKnowledgeCode());
        } catch (IOException e) {
            log.error("获取文档内容失败", e);
            throw new RuntimeException("获取文档内容失败", e);
        }

        AiKnowledgeAttachDo aiKnowledgeAttachDo = AiKnowledgeAttachDo.builder()
            .knowledgeCode(knowledgeCode)
            .docCode(docId)
            .docName(fileName)
            .docType(fileType)
            .filePath(targetPath.toString())
            .publicUrl(downloadUrl)
            .content(content)
            .build();

        List<String> fragmentCodes = new ArrayList<>();
        List<AiKnowledgeFragmentDo> knowledgeFragmentList = new ArrayList<>();

        if (CollUtil.isNotEmpty(chunkList)) {
            for (int i = 0; i < chunkList.size(); i++) {
                String fragmentCode = RandomUtil.randomString(10);
                fragmentCodes.add(fragmentCode);
                AiKnowledgeFragmentDo knowledgeFragment = AiKnowledgeFragmentDo.builder()
                    .knowledgeCode(knowledgeCode)
                    .docCode(docId)
                    .fragmentCode(fragmentCode)
                    .idx(i)
                    .content(chunkList.get(i))
                    .build();
                knowledgeFragmentList.add(knowledgeFragment);
            }
        }
        // 查询知识库信息
        AiKnowledgeInfoDo knowledgeInfoDo = knowledgeInfoMapper.selectOne(new LambdaQueryWrapper<AiKnowledgeInfoDo>()
            .eq(AiKnowledgeInfoDo::getKnowledgeCode, knowledgeCode));
        AiChatModelDo aiChatModelDo = chatModelMapper.selectOne(new LambdaQueryWrapper<AiChatModelDo>().eq(AiChatModelDo::getModelName, knowledgeInfoDo.getEmbeddingModelName()));
        AiStoreEmbeddingBo aiStoreEmbeddingBo = AiStoreEmbeddingBo.builder()
            .kid(knowledgeCode)
            .docId(docId)
            .fids(fragmentCodes)
            .chunkList(chunkList)
            .vectorModelName(knowledgeInfoDo.getVectorModelName())
            .embeddingModelName(knowledgeInfoDo.getEmbeddingModelName())
            .apiKey(aiChatModelDo.getApiKey())
            .baseUrl(aiChatModelDo.getApiHost())
            .fileName(fileName)
            .build();
        batchInsertKnowledgeAttachAndFragment(List.of(aiKnowledgeAttachDo), knowledgeFragmentList);
        vectorStoreService.storeEmbeddings(aiStoreEmbeddingBo);
    }

    @Transactional
    protected void batchInsertKnowledgeAttachAndFragment(List<AiKnowledgeAttachDo> attachList,
                                                         List<AiKnowledgeFragmentDo> fragmentList) {
        knowledgeAttachMapper.insertBatch(attachList);
        knowledgeFragmentMapper.insertBatch(fragmentList);
    }

}
