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


import cn.iocoder.yudao.framework.common.exception.ErrorCode;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
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.enums.KnowledgeStatusEnum;
import cn.iocoder.yudao.module.llm.framework.backend.config.LLMBackendProperties;
import cn.iocoder.yudao.module.llm.service.http.vo.*;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
import com.google.gson.JsonArray;
import kong.unirest.HttpResponse;
import kong.unirest.Unirest;
import kong.unirest.UnirestException;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.mozilla.universalchardet.UniversalDetector;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;

/**
 * rag相关接口
 */
@Service
@Slf4j
public class RagHttpService {

    @Resource
    private LLMBackendProperties llmBackendProperties;

    @Resource
    private KnowledgeBaseMapper knowledgeBaseMapper;

    @Resource
    private KnowledgeDocumentsMapper knowledgeDocumentsMapper;

    /**
     * RAG健康检查API
     */
    public RagHealthRespVO ragHealth (Map<String, String> headers) {
        String ragHealth = llmBackendProperties.getRagHealth();
        String res = HttpUtils.get(ragHealth, headers);
        log.info(" ragHealth:{}", res);
        RagHealthRespVO regHealthRespVO = JSON.parseObject(res, RagHealthRespVO.class);
        log.info(" ragHealthRespVO:{}", regHealthRespVO);
        return regHealthRespVO;
    }

    /**
     * 上传并向量化
     */
    public RagEmbedRespVO ragEmbed (Map<String, String> headers, RagEmbedReqVo ragEmbedReqVo) {
        String ragEmbed = llmBackendProperties.getRagEmbed();
        Map<String, Object> map = BeanUtils.beanToMap(ragEmbedReqVo);
        String res = HttpUtils.postForm(ragEmbed, headers, map);
        log.info(" ragEmbedRespVO:{}", res);
        RagEmbedRespVO ragEmbedRespVO = JSON.parseObject(res, RagEmbedRespVO.class);
        log.info(" ragEmbedRespVO:{}", ragEmbedRespVO);
        return ragEmbedRespVO;
    }

    /**
     * 向量知识库文档上传
     *
     * @param ragUploadReqVO
     * @throws UnirestException
     * @throws IOException
     */
    public void embedUploadFile (RegUploadReqVO ragUploadReqVO) throws UnirestException, IOException {
        // 根据 fileId 查询知识库文档
        KnowledgeDocumentsDO documents = getKnowledgeDocuments(ragUploadReqVO.getFileId());
        if (documents == null) {
            throw exception(new ErrorCode(10047, "知识库文档不存在"));
        }

        // 修改状态为 上传中
        updateFileState(documents, KnowledgeStatusEnum.UPLOADING);

        CloseableHttpClient httpClient = HttpClients.createDefault();
        RagEmbedRespVO ragEmbedRespVO = new RagEmbedRespVO();
        HttpGet request = new HttpGet(ragUploadReqVO.getFileUrl());
        try (CloseableHttpResponse response = httpClient.execute(request)) {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                try (InputStream inputStream = entity.getContent();
                     BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
                    bufferedInputStream.mark(Integer.MAX_VALUE);
                    String encoding = detectCharset(bufferedInputStream);
                    bufferedInputStream.reset(); // 重置流以便重新读取
                    // 使用检测到的编码读取文件内容
                    try (InputStreamReader reader = new InputStreamReader(bufferedInputStream, encoding);
                         BufferedReader bufferedReader = new BufferedReader(reader)) {
                        StringBuilder fileContentBuilder = new StringBuilder();
                        String line;
                        while ((line = bufferedReader.readLine()) != null) {
                            fileContentBuilder.append(line).append(System.lineSeparator());
                        }
                        String fileContent = fileContentBuilder.toString();
                        byte[] utf8Bytes = fileContent.getBytes(StandardCharsets.UTF_8);

                        // 上传文件
                        Unirest.config().reset();
                        Unirest.config().socketTimeout(60000);
                        HttpResponse<String> uploadResponse = Unirest.post(ragUploadReqVO.getUrl())
                                .field("file_id", ragUploadReqVO.getFileId())
                                .field("file", new ByteArrayInputStream(utf8Bytes), ragUploadReqVO.getFileName()) // 使用文件名 "file.txt" 作为示例
                                .asString();

                        log.info("Response Body: {}", uploadResponse.getBody());
                        ragEmbedRespVO = JSON.parseObject(uploadResponse.getBody(), RagEmbedRespVO.class);
                        log.info("ragEmbedRespVO:{}", ragEmbedRespVO);

                        if (ragEmbedRespVO.isStatus()) {
                            // 修改状态为 上传成功
                            updateFileState(documents, KnowledgeStatusEnum.UPLOAD_SUCCESS);
                        } else {
                            // 修改状态为 上传失败
                            updateFileState(documents, KnowledgeStatusEnum.UPLOAD_FAILED);
                            throw new RuntimeException("文件上传失败:"+ragEmbedRespVO.getMessage());
                        }
                    }
                }
            }
        }
    }

    private static String detectCharset (InputStream inputStream) throws IOException {
        byte[] buffer = new byte[4096];
        int nread;
        UniversalDetector detector = new UniversalDetector(null);
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        while ((nread = bis.read(buffer)) > 0 && !detector.isDone()) {
            detector.handleData(buffer, 0, nread);
        }
        detector.dataEnd();
        String charset = detector.getDetectedCharset();
        detector.reset();
        if (charset == null) {
            return StandardCharsets.UTF_8.toString();
        }
        return charset;
    }

    public String ragDocumentsDel (String url, List<Long> documentIds) {
        // 创建JSON数组
        JsonArray jsonArray = new JsonArray();
        for (Long id : documentIds) {
            jsonArray.add(String.valueOf(id));
        }
        // 发送DELETE请求
        HttpResponse<String> response = Unirest.delete(url)
                .header("Content-Type", "application/json")
                .body(jsonArray.toString())
                .asString();
        // 返回响应体
        return response.getBody();
    }

    /**
     * 获取所有向量id
     */
    public List<String> ragIds (Map<String, String> headers) {
        String ragIds = llmBackendProperties.getRagIds();
        String res = HttpUtils.get(ragIds, headers);
        log.info(" ragIds:{}", res);
        List<String> ids = JSON.parseArray(res, String.class);
        log.info(" ragIds:{}", ids);
        return ids;
    }

    /**
     * 根据id获取文档
     */
    public RagDocumentsRespVO ragDocuments (Map<String, String> headers, RagDocumentsReqVO ragDocumentsReqVO) {
        String ragDocuments = llmBackendProperties.getRagDocuments();
        String res = HttpUtils.get(ragDocuments, headers);
        log.info(" ragDocuments:{}", res);
        List<DocumentPageContent> pages = JSON.parseArray(res, DocumentPageContent.class);
        RagDocumentsRespVO ragDocumentRespVO = new RagDocumentsRespVO();
        ragDocumentRespVO.setPages(pages);
        log.info(" ragDocumentRespVO:{}", ragDocumentRespVO);
        return ragDocumentRespVO;
    }

    /**
     * 根据id删除文档
     */
    public String ragDocumentsDel (Map<String, String> headers) {
        String ragDocumentsDel = llmBackendProperties.getRagDocumentsDel();
        String res = HttpUtils.del(ragDocumentsDel, headers);
        log.info(" ragDocumentsDel:{}", res);
        return res;
    }

    /**
     * 根据file_id检索向量
     */
    public RagQueryRespVO ragQuery (Map<String, String> headers, RagQueryReqVo ragQueryReqVo) {
        String ragQuery = llmBackendProperties.getRagQuery();
        String res = HttpUtils.post(ragQuery, headers, JSON.toJSONString(ragQueryReqVo));
        log.info(" ragQuery:{}", res);
        JSONArray jsonArray = JSON.parseArray(res);
        List<DocumentPageContent> pages = JSON.parseArray(jsonArray.getJSONObject(0).getString("page_content"), DocumentPageContent.class);
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONArray temp = jsonArray.getJSONArray(i);
            DocumentPageContent documentPageContent = JSON.toJavaObject(temp.getJSONObject(0), DocumentPageContent.class);
            documentPageContent.setScore(String.valueOf(temp.getDouble(1)));
        }
        RagQueryRespVO ragQueryRespVO = new RagQueryRespVO();
        ragQueryRespVO.setPages(pages);
        log.info(" ragQueryRespVO:{}", ragQueryRespVO);
        return ragQueryRespVO;
    }

    /**
     * 支持多个文件id查询向量
     */
    public String ragQueryMultiple (Map<String, String> headers, RagQueryMultipleReqVo ragQueryReqVo) {
        String ragQueryMultiple = llmBackendProperties.getRagQueryMultiple();
        String res = HttpUtils.getBody(ragQueryMultiple, headers, JSON.toJSONString(ragQueryReqVo));
        return res;
    }

    /**
     * 知识库向量嵌入
     *
     * @param reqVO 请求参数
     * @param id
     */
    public void knowledgeEmbed (KnowledgeRagEmbedReqVO reqVO, Long id) {
        // 获取知识库向量嵌入的url
        String ragEmbed = llmBackendProperties.getEmbed();

        log.info("url : {}", ragEmbed);

        // fileId 是 llm_knowledge_documents 表 ID
        String fileId = reqVO.getFileId();
        String fileName = reqVO.getFileName();

        // 根据 fileId 查询知识库文档
        KnowledgeDocumentsDO documents = getKnowledgeDocuments(id, fileId);
        if (documents == null) {
            throw exception(new ErrorCode(10047, "知识库文档不存在"));
        }

        // 修改状态为 上传中
        updateFileState(documents, KnowledgeStatusEnum.UPLOADING);

        // 构建请求参数
        HttpResponse<String> response = Unirest.post(ragEmbed)
                .field("file_id", fileId)
                .field("file", reqVO.getFileInputStream(), fileName)
                .asString();

        String responseBody = response.getBody();
        JSONObject parseObject = JSON.parseObject(responseBody);
        log.info(" ========= Response Body Result: {}", responseBody);

        if (parseObject.getBoolean("status")) {
            // 修改状态为 上传成功
            updateFileState(documents, KnowledgeStatusEnum.UPLOAD_SUCCESS);
        } else {
            // 修改状态为 上传失败
            updateFileState(documents, KnowledgeStatusEnum.UPLOAD_FAILED);
            throw exception(new ErrorCode(10047, responseBody));
        }

    }

    /**
     * 修改知识库文档状态
     *
     * @param documents 知识库文档
     * @param status    状态 (0:未上传，1:上传中，2:上传成功，3:上传失败)
     */
    private void updateFileState (KnowledgeDocumentsDO documents, KnowledgeStatusEnum status) {
        documents.setStatus(status.getStatus());
        knowledgeDocumentsMapper.updateById(documents);
    }

    /**
     * 获取知识库文档对象
     *
     * @param id     知识库ID
     * @param fileId 知识库文档ID
     * @return 知识库对象
     */
    private KnowledgeDocumentsDO getKnowledgeDocuments (Long id, String fileId) {
        return knowledgeDocumentsMapper.selectOne(new LambdaQueryWrapperX<KnowledgeDocumentsDO>()
                .eq(KnowledgeDocumentsDO::getKnowledgeBaseId, id)
                .eq(KnowledgeDocumentsDO::getId, fileId));
    }

    /**
     * 获取知识库文档对象
     *
     * @param fileId 知识库ID
     * @return 知识库对象
     */
    private KnowledgeDocumentsDO getKnowledgeDocuments (String fileId) {
        return knowledgeDocumentsMapper.selectById(fileId);
    }
}
