package cn.iocoder.yudao.module.llm.service.knowledgebase;

import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBasePageReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgebase.vo.KnowledgeBaseSaveReqVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgedocuments.vo.KnowledgeDocumentsRespVO;
import cn.iocoder.yudao.module.llm.controller.admin.knowledgedocuments.vo.KnowledgeDocumentsSaveReqVO;
import cn.iocoder.yudao.module.llm.dal.dataobject.knowledgebase.KnowledgeBaseDO;
import cn.iocoder.yudao.module.llm.dal.dataobject.knowledgedocuments.KnowledgeDocumentsDO;
import cn.iocoder.yudao.module.llm.dal.mysql.knowledgebase.KnowledgeBaseMapper;
import cn.iocoder.yudao.module.llm.dal.mysql.knowledgedocuments.KnowledgeDocumentsMapper;
import cn.iocoder.yudao.module.llm.service.application.ApplicationService;
import cn.iocoder.yudao.module.llm.service.async.AsyncKnowledgeBase;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import kong.unirest.Unirest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.annotation.Tainted;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.llm.enums.ErrorCodeConstants.KNOWLEDGE_BASE_NAME_NOT_EXISTS;
import static cn.iocoder.yudao.module.llm.enums.ErrorCodeConstants.KNOWLEDGE_BASE_NOT_EXISTS;

/**
 * 知识库 Service 实现类
 *
 * @author 华大大模型
 */
@Slf4j
@Service
@Validated
public class KnowledgeBaseServiceImpl implements KnowledgeBaseService {

    @Resource
    private KnowledgeBaseMapper knowledgeBaseMapper;
    @Resource
    private KnowledgeDocumentsMapper knowledgeDocumentsMapper;
    @Resource
    private AsyncKnowledgeBase asyncKnowledgeBase;
    @Autowired
    private ApplicationService applicationService;

    @Override
    public Long createKnowledgeBase (KnowledgeBaseSaveReqVO createReqVO) {
        validateKnowledgeBaseNameExists(createReqVO);
        // 插入
        KnowledgeBaseDO knowledgeBase = BeanUtils.toBean(createReqVO, KnowledgeBaseDO.class);
        knowledgeBaseMapper.insert(knowledgeBase);
        // 返回
        return knowledgeBase.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateKnowledgeBase (KnowledgeBaseSaveReqVO updateReqVO) {
        // 1. 校验知识库是否存在
        validateKnowledgeBaseExists(updateReqVO.getId());

        // 2. 校验知识库名称是否重复
        validateKnowledgeBaseNameExists(updateReqVO);

        // 3. 更新知识库主表
        KnowledgeBaseDO updateObj = BeanUtils.toBean(updateReqVO, KnowledgeBaseDO.class);
        knowledgeBaseMapper.updateById(updateObj);

        Unirest.config().reset();
        Unirest.config()
                .socketTimeout(86400000)
                .connectTimeout(100000)
                .concurrency(10, 5)
                .setDefaultHeader("Accept", "application/json");

        // 4. 处理附表（知识文档）数据
        if (!CollectionUtils.isAnyEmpty(updateReqVO.getKnowledgeDocuments())) {
            // 4.1 获取需要保留的文档 ID
            List<Long> retainedIds = updateReqVO.getKnowledgeDocuments().stream()
                    .map(KnowledgeDocumentsSaveReqVO::getId)
                    .filter(Objects::nonNull)
                    .collect(Collectors.toList());

            // 4.2 删除不需要保留的文档
            LambdaQueryWrapperX<KnowledgeDocumentsDO> deleteWrapper = new LambdaQueryWrapperX<KnowledgeDocumentsDO>()
                    .eq(KnowledgeDocumentsDO::getKnowledgeBaseId, updateReqVO.getId());
            if (!CollectionUtils.isAnyEmpty(retainedIds)) {
                deleteWrapper.notIn(KnowledgeDocumentsDO::getId, retainedIds);
            }
            knowledgeDocumentsMapper.delete(deleteWrapper);

            // 4.3 更新或插入文档数据
            List<KnowledgeDocumentsDO> newDocuments = new ArrayList<>();
            updateReqVO.getKnowledgeDocuments().forEach(doc -> {
                KnowledgeDocumentsDO docDO = BeanUtils.toBean(doc, KnowledgeDocumentsDO.class);
                docDO.setKnowledgeBaseId(updateReqVO.getId());
                if (doc.getId() == null) {
                    newDocuments.add(docDO); // 收集新增文档
                }
                knowledgeDocumentsMapper.insertOrUpdate(docDO); // 更新或插入文档
            });

            // 4.4 异步处理新增文档和删除的文档
            List<Long> deleteIds = knowledgeDocumentsMapper.selectDeleteIds(updateReqVO.getId());
            log.info("注释..");
            asyncKnowledgeBase.createKnowledgeBase(newDocuments, deleteIds);
        } else {
            // 5. 如果传入的文档列表为空，则删除所有关联文档
            knowledgeDocumentsMapper.delete(new LambdaQueryWrapperX<KnowledgeDocumentsDO>()
                    .eq(KnowledgeDocumentsDO::getKnowledgeBaseId, updateReqVO.getId()));

            // 5.1 异步处理删除的文档
            List<Long> deleteIds = knowledgeDocumentsMapper.selectDeleteIds(updateReqVO.getId());
            if (!CollectionUtils.isAnyEmpty(deleteIds)) {
                asyncKnowledgeBase.createKnowledgeBase(null, deleteIds);
            }
        }

    }

    @Override
    public void deleteKnowledgeBase (Long id) {
        // 校验存在
        validateKnowledgeBaseExists(id);
        // 校验使用
        validateKnowledgeBaseUse(id);
        // 删除
        knowledgeBaseMapper.deleteById(id);
    }

    /**
     * 校验使用
     *
     * @param id id
     */
    private void validateKnowledgeBaseUse (Long id) {
        // 获取 知识库信息，用来返回错误信息
        KnowledgeBaseDO knowledgeBaseDO = knowledgeBaseMapper.selectById(id);
        String name = knowledgeBaseDO.getKnowledgeBaseName();

        // 检查 知识库 是否在 应用 中有使用
        validateKnowledgeBaseUsesInApp(id, name);
    }

    /**
     * 检查 知识库 是否在 应用 中有使用
     *
     * @param id   知识库 id
     * @param name 知识库 名称
     */
    private void validateKnowledgeBaseUsesInApp (Long id, String name) {
        Map<Long, String> apps = applicationService.getApplicationByKnowledgeId(id);

        if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(apps)) {
            String msg = String.format("知识库【%s】在 应用 %s 有使用，请先删除应用中的知识库", name, apps.values());
            throw exception(new ErrorCode(10001_001, msg));
        }
    }

    private void validateKnowledgeBaseExists (Long id) {
        if (knowledgeBaseMapper.selectById(id) == null) {
            throw exception(KNOWLEDGE_BASE_NOT_EXISTS);
        }
    }

    private void validateKnowledgeBaseNameExists (KnowledgeBaseSaveReqVO reqVO) {
        LambdaQueryWrapper<KnowledgeBaseDO> wrapper = new LambdaQueryWrapper<KnowledgeBaseDO>()
                .eq(KnowledgeBaseDO::getKnowledgeBaseName, reqVO.getKnowledgeBaseName());

        if (reqVO.getId() != null) {
            wrapper.ne(KnowledgeBaseDO::getId, reqVO.getId());
        }
        List<KnowledgeBaseDO> knowledgeBaseDos = knowledgeBaseMapper.selectList(wrapper);
        if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(knowledgeBaseDos)) {
            throw exception(KNOWLEDGE_BASE_NAME_NOT_EXISTS);
        }
    }

    @Override
    public KnowledgeBaseDO getKnowledgeBase (Long id) {
        return knowledgeBaseMapper.selectById(id);
    }

    @Override
    public PageResult<KnowledgeBaseDO> getKnowledgeBasePage (KnowledgeBasePageReqVO pageReqVO) {
        return knowledgeBaseMapper.selectPage(pageReqVO);
    }

    @Override
    public List<KnowledgeBaseDO> getKnowledgeBaseList () {
        return knowledgeBaseMapper.selectList();
    }

    @Override
    public KnowledgeBaseRespVO getKnowledgeBase1 (Long id) {
        KnowledgeBaseDO knowledgeBaseDO = knowledgeBaseMapper.selectById(id);
        if (knowledgeBaseDO != null) {
            KnowledgeBaseRespVO knowledgeBaseRespVO = BeanUtils.toBean(knowledgeBaseDO, KnowledgeBaseRespVO.class);
            List<KnowledgeDocumentsDO> knowledgeDocuments = knowledgeDocumentsMapper.selectList(
                    KnowledgeDocumentsDO::getKnowledgeBaseId, id);
            knowledgeBaseRespVO.setKnowledgeDocuments(BeanUtils.toBean(knowledgeDocuments, KnowledgeDocumentsRespVO.class));
            return knowledgeBaseRespVO;
        }
        return null;
    }

    /**
     * 更新知识库信息
     *
     * @param updateReqVO 更新信息
     */
    @Override
    public void updateKnowledgeBaseInfo (KnowledgeBaseSaveReqVO updateReqVO) {
        // 1. 校验知识库是否存在
        KnowledgeBaseDO knowledgeBaseDO = validateKnowledgeInfo(updateReqVO.getId());

        // 2. 校验知识库名称是否重复
        validateKnowledgeBaseNameExists(updateReqVO);

        // 3. 更新知识库主表
        updateKnowledgeBaseFields(knowledgeBaseDO, updateReqVO);

        // 4. 执行更新
        knowledgeBaseMapper.updateById(knowledgeBaseDO);
    }

    /**
     * 校验知识库是否存在
     *
     * @param id 知识库ID
     * @return 知识库实体
     */
    private KnowledgeBaseDO validateKnowledgeInfo (Long id) {
        KnowledgeBaseDO knowledgeBaseDO = knowledgeBaseMapper.selectById(id);
        if (knowledgeBaseDO == null) {
            throw exception(KNOWLEDGE_BASE_NOT_EXISTS);
        }
        return knowledgeBaseDO;
    }

    /**
     * 更新知识库字段
     *
     * @param knowledgeBaseDO 知识库实体
     * @param updateReqVO     更新请求VO
     */
    private void updateKnowledgeBaseFields (KnowledgeBaseDO knowledgeBaseDO, KnowledgeBaseSaveReqVO updateReqVO) {
        // 基础信息只有名称、简介、logo会修改
        knowledgeBaseDO.setKnowledgeBaseName(updateReqVO.getKnowledgeBaseName())
                .setKnowledgeBaseIntro(updateReqVO.getKnowledgeBaseIntro())
                .setKnowledgeBaseLogo(updateReqVO.getKnowledgeBaseLogo());
    }

}
