package com.cy.guoyan.admin.module.ai.service.chatknowledgedataset;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cy.guoyan.admin.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cy.guoyan.admin.module.ai.controller.admin.chatknowledgedataset.vo.ChatKnowledgeDatasetPageReqVO;
import com.cy.guoyan.admin.module.ai.controller.admin.chatknowledgedataset.vo.ChatKnowledgeDatasetSaveReqVO;
import com.cy.guoyan.admin.module.ai.dal.dataobject.chatdocument.ChatDocumentDO;
import com.cy.guoyan.admin.module.ai.dal.mysql.chatdocument.ChatDocumentMapper;
import com.cy.guoyan.admin.module.ai.util.DifyClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import com.cy.guoyan.admin.module.ai.dal.dataobject.chatknowledgedataset.ChatKnowledgeDatasetDO;
import com.cy.guoyan.admin.framework.common.pojo.PageResult;
import com.cy.guoyan.admin.framework.common.util.object.BeanUtils;
import com.cy.guoyan.admin.module.ai.dal.mysql.chatknowledgedataset.ChatKnowledgeDatasetMapper;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import static com.cy.guoyan.admin.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cy.guoyan.admin.module.system.enums.ErrorCodeConstants.*;
import static java.nio.charset.StandardCharsets.UTF_8;

/**
 * AI 知识库 Service 实现类
 *
 * @author 管理员
 */
@Service
@Validated
@Slf4j
public class ChatKnowledgeDatasetServiceImpl implements ChatKnowledgeDatasetService {

    @Resource
    private ChatKnowledgeDatasetMapper chatKnowledgeDatasetMapper;

    @Value("${dify.datasetsKey}")
    private String difyApiKey;

    @Value("${dify.baseUrl}")
    private String difyBaseUrl;

    @Resource
    private ChatDocumentMapper chatDocumentMapper;

    private final DifyClient difyClient;

    public ChatKnowledgeDatasetServiceImpl(DifyClient difyClient) {
        this.difyClient = difyClient;
    }

    private HttpURLConnection prepare(String method, String path) throws IOException {
        URL url = new URL(difyBaseUrl + path);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod(method);
        conn.setRequestProperty("Authorization", "Bearer " + difyApiKey);
        return conn;
    }

    /** 读取响应 body 或抛异常 */
    private String readResponse(HttpURLConnection conn) throws IOException {
        int code = conn.getResponseCode();
        InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
        if (is == null) throw new IOException("无返回流，HTTP " + code);
        String body;
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) {
            body = reader.lines().collect(Collectors.joining());
        }
        if (code < 200 || code >= 300) {
            throw new IOException("Dify 错误 HTTP " + code + "，" + body);
        }
        return body;
    }

    private LocalDateTime epoch2Local(Long epoch) {
        return epoch == null ? null : LocalDateTime.ofInstant(
                Instant.ofEpochSecond(epoch), ZoneId.systemDefault());
    }

    @Override
    public Long createChatKnowledgeDataset(ChatKnowledgeDatasetSaveReqVO vo)throws IOException {
        JSONObject body = new JSONObject();
        body.put("name", vo.getName());
        body.put("description", vo.getDescription());
        body.put("indexing_technique", "high_quality");
        body.put("embedding_model", vo.getEmbeddingModel());
        body.put("auto_update", vo.getAutoUpdate());
        body.put("provider", "vendor");
        body.put("permission", "all_team_members");

        HttpURLConnection conn = prepare("POST", "/v1/datasets");
        conn.setDoOutput(true);
        conn.setRequestProperty("Content-Type", "application/json");
        try (OutputStream os = conn.getOutputStream()) {
            os.write(body.toJSONString().getBytes(UTF_8));
        }
        String resp = readResponse(conn);
        JSONObject j = JSONObject.parseObject(resp);

        ChatKnowledgeDatasetDO db = ChatKnowledgeDatasetDO.builder()
                .difyDatasetId(j.getString("id"))
                .name(j.getString("name"))
                .description(j.getString("description"))
                .indexingTechnique(j.getString("indexing_technique"))
                .embeddingModel(j.getString("embedding_model"))
                .autoUpdate(j.getBoolean("auto_update"))
                .enabled(j.getBoolean("enabled"))
                .defaultPermission(j.getString("default_permission"))
                .appCount(j.getIntValue("app_count"))
                .difyCreatedAt(epoch2Local(j.getLong("created_at")))
                .difyUpdatedAt(epoch2Local(j.getLong("updated_at")))
                .metadataJson(j.toJSONString())
                .build();
        chatKnowledgeDatasetMapper.insert(db);
        return db.getId();
    }

    @Override
    public void updateChatKnowledgeDataset(ChatKnowledgeDatasetSaveReqVO updateReqVO) {
        // 校验存在
        validateChatKnowledgeDatasetExists(updateReqVO.getId());
        // 更新
        ChatKnowledgeDatasetDO updateObj = BeanUtils.toBean(updateReqVO, ChatKnowledgeDatasetDO.class);
        chatKnowledgeDatasetMapper.updateById(updateObj);
    }

    @Override
    public void deleteChatKnowledgeDataset(Long datasetId) throws IOException {
        ChatKnowledgeDatasetDO datasetDO = chatKnowledgeDatasetMapper.selectById(datasetId);
        if (datasetDO == null) {
            throw exception(CHAT_KNOWLEDGE_DATASET_NOT_EXISTS);
        }

        HttpURLConnection conn = prepare("DELETE", "/v1/datasets/" + datasetDO.getDifyDatasetId());
        log.info("删除知识库：{}", datasetDO.getDifyDatasetId());
        readResponse(conn);

        chatKnowledgeDatasetMapper.deleteById(datasetId);
    }

    private void validateChatKnowledgeDatasetExists(Long id) {
        if (chatKnowledgeDatasetMapper.selectById(id) == null) {
            throw exception(CHAT_KNOWLEDGE_DATASET_NOT_EXISTS);
        }
    }

    @Override
    public ChatKnowledgeDatasetDO getChatKnowledgeDataset(Long id) {
        return chatKnowledgeDatasetMapper.selectById(id);
    }

    @Override
    public PageResult<ChatKnowledgeDatasetDO> getChatKnowledgeDatasetPage(ChatKnowledgeDatasetPageReqVO pageReqVO) {
        return chatKnowledgeDatasetMapper.selectPage(pageReqVO);
    }

    @Override
    public int syncFromDify() throws IOException {
        // 1. 调用 Dify 接口获取所有知识库
        URL url = new URL(difyBaseUrl + "/v1/datasets");
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setRequestProperty("Authorization", "Bearer " + difyApiKey);
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(10000);

        int status = conn.getResponseCode();
        InputStream is = (status >= 200 && status < 300) ? conn.getInputStream() : conn.getErrorStream();
        if (is == null) {
            throw new IOException("同步失败，无法获取响应流，HTTP 状态：" + status);
        }

        String resp;
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, UTF_8))) {
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
            resp = sb.toString();
        }

        if (status < 200 || status >= 300) {
            throw new IOException("同步失败，Dify 返回错误: HTTP " + status + "，响应: " + resp);
        }

        JSONObject json = JSONObject.parseObject(resp); // 先解析为对象
        JSONArray arr = json.getJSONArray("data"); // 提取其中的数组字段 data
        int count = 0;

        for (int i = 0; i < arr.size(); i++) {
            JSONObject j = arr.getJSONObject(i);
            String did = j.getString("id");

            // 3. 构造 DO
            ChatKnowledgeDatasetDO db = new ChatKnowledgeDatasetDO();
            db.setDifyDatasetId(did);
            db.setName(j.getString("name"));
            db.setDescription(j.getString("description"));
            db.setIndexingTechnique(j.getString("indexing_technique"));
            db.setEmbeddingModel(j.getString("embedding_model"));
            db.setAutoUpdate(j.getBoolean("auto_update"));
            db.setEnabled(j.getBoolean("enabled"));
            db.setDefaultPermission(j.getString("default_permission"));
            db.setAppCount(j.getIntValue("app_count"));

            // Dify 返回的是秒级时间戳
            Long createdAt = j.getLong("created_at");
            if (createdAt != null) {
                db.setDifyCreatedAt( LocalDateTime.ofInstant(
                        Instant.ofEpochSecond(createdAt), ZoneId.systemDefault()));
            }
            Long updatedAt = j.getLong("updated_at");
            if (updatedAt != null) {
                db.setDifyUpdatedAt( LocalDateTime.ofInstant(
                        Instant.ofEpochSecond(updatedAt), ZoneId.systemDefault()));
            }

            db.setMetadataJson(j.toJSONString());

            // 4. 本地 upsert
            ChatKnowledgeDatasetDO exist = chatKnowledgeDatasetMapper.selectOne(
                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ChatKnowledgeDatasetDO>()
                            .eq(ChatKnowledgeDatasetDO::getDifyDatasetId, did)
            );
            if (exist == null) {
                chatKnowledgeDatasetMapper.insert(db);
            } else {
                db.setId(exist.getId());
                chatKnowledgeDatasetMapper.updateById(db);
            }
            count++;
        }

        return count;
    }

    @Override
    public List<ChatKnowledgeDatasetDO> getChatKnowledgeDatasetList(ChatKnowledgeDatasetPageReqVO reqVO) {

        List<ChatKnowledgeDatasetDO> datasetDOS = chatKnowledgeDatasetMapper.selectList(new LambdaQueryWrapperX<ChatKnowledgeDatasetDO>()
                .betweenIfPresent(ChatKnowledgeDatasetDO::getCreateTime, reqVO.getCreateTime())
                .eqIfPresent(ChatKnowledgeDatasetDO::getDifyDatasetId, reqVO.getDifyDatasetId())
                .likeIfPresent(ChatKnowledgeDatasetDO::getName, reqVO.getName())
                .eqIfPresent(ChatKnowledgeDatasetDO::getDescription, reqVO.getDescription())
                .eqIfPresent(ChatKnowledgeDatasetDO::getIndexingTechnique, reqVO.getIndexingTechnique())
                .eqIfPresent(ChatKnowledgeDatasetDO::getEmbeddingModel, reqVO.getEmbeddingModel())
                .betweenIfPresent(ChatKnowledgeDatasetDO::getAutoUpdate, reqVO.getAutoUpdate())
                .eqIfPresent(ChatKnowledgeDatasetDO::getEnabled, reqVO.getEnabled())
                .eqIfPresent(ChatKnowledgeDatasetDO::getDefaultPermission, reqVO.getDefaultPermission())
                .eqIfPresent(ChatKnowledgeDatasetDO::getAppCount, reqVO.getAppCount())
                .eqIfPresent(ChatKnowledgeDatasetDO::getDifyCreatedAt, reqVO.getDifyCreatedAt())
                .eqIfPresent(ChatKnowledgeDatasetDO::getDifyUpdatedAt, reqVO.getDifyUpdatedAt())
                .eqIfPresent(ChatKnowledgeDatasetDO::getMetadataJson, reqVO.getMetadataJson())
                .orderByAsc(ChatKnowledgeDatasetDO::getId));

        List<String> list = datasetDOS.stream().map(ChatKnowledgeDatasetDO::getDifyDatasetId).collect(Collectors.toList());
        if (list.size() > 0) {
            List<ChatDocumentDO> chatDocumentDOS = chatDocumentMapper.selectList(new LambdaQueryWrapperX<ChatDocumentDO>().in(ChatDocumentDO::getDatasetId, list));
            datasetDOS.forEach(f->{
                f.setAppCount(chatDocumentDOS.stream().filter(d->d.getDatasetId().equals(f.getDifyDatasetId())).collect(Collectors.toList()).size());
            });
        }

        return datasetDOS;
    }


    @Override
    public JSONObject createDocumentByFile(MultipartFile file, String dataJson, String datasetId) throws IOException {


        File tempFile = null;

            if (file.isEmpty()) {
                throw new IOException("上传文件不能为空");
            }
            // 1. 处理中文文件名编码
            String originalFilename = file.getOriginalFilename();
            String encodedFilename = "UTF-8''" +
                    URLEncoder.encode(originalFilename, StandardCharsets.UTF_8.name())
                            .replaceAll("\\+", "%20");  // 替换 + 为 RFC 兼容的 %20

            System.out.println("文件名: " + file.getOriginalFilename());
            System.out.println("文件大小: " + file.getSize());
            // 转换 MultipartFile 为临时文件
            tempFile = File.createTempFile("dify_upload_", "_" + file.getOriginalFilename());
            file.transferTo(tempFile);
            tempFile.deleteOnExit(); // 程序退出时删除

            // 构建请求元数据
            Map<String, Object> requestData = new HashMap<>();
            requestData.put("datasetId", datasetId);  // 关键字段
            requestData.put("name", originalFilename);  // 关键字段
            requestData.put("indexing_technique", "high_quality");
            requestData.put("doc_form", "text_model");

            Map<String, Object> processRule = new HashMap<>();
            processRule.put("mode", "custom");

            Map<String, Object> rules = new HashMap<>();

            // 预处理规则
            List<Map<String, Object>> preProcessingRules = new ArrayList<>();

            Map<String, Object> rule1 = new HashMap<>();
            rule1.put("id", "remove_extra_spaces");
            rule1.put("enabled", true);
            preProcessingRules.add(rule1);

            Map<String, Object> rule2 = new HashMap<>();
            rule2.put("id", "remove_urls_emails");
            rule2.put("enabled", true);
            preProcessingRules.add(rule2);

            rules.put("pre_processing_rules", preProcessingRules);

            // 分段规则
            Map<String, Object> segmentation = new HashMap<>();
            segmentation.put("separator", "###");
            segmentation.put("max_tokens", 500);
            rules.put("segmentation", segmentation);

            processRule.put("rules", rules);
            requestData.put("process_rule", processRule);

            JSONObject documentByFile = difyClient.createDocumentByFile(tempFile, encodedFilename, requestData);

            return documentByFile;
    }

    @Override
    public JSONObject getDocumentIndexingStatus(String datasetId, String batch) throws IOException {
        return difyClient.getDocumentIndexingStatus(datasetId, batch);

    }

    @Override
    public void deleteDocument(String datasetId, String documentId) throws IOException {

        difyClient.deleteDocument(datasetId, documentId);
    }

    @Override
    public JSONObject addDocumentSegments(String datasetId, String documentId, List<Map<String, Object>> segments1) throws IOException {

        List<Map<String, Object>> segments = new ArrayList<>();
        Map<String, Object> segment = new HashMap<>();
        segment.put("content", "上海");
        segment.put("answer", "20万"); // 可选
        segment.put("keywords", Arrays.asList("烟", "双喜")); // 可选
        segments.add(segment);

       return difyClient.addDocumentSegments(datasetId, documentId, segments);

    }

    @Override
    public JSONObject listDocuments(String datasetId, String keyword, Integer page, Integer limit) throws IOException {

        return difyClient.listDocuments(datasetId, keyword, page, limit);

    }

    @Override
    public JSONObject listDocumentSegments(String datasetId, String documentId, String keyword, String status) throws IOException {

        return difyClient.listDocumentSegments(datasetId, documentId, keyword, status);

    }

    @Override
    public void deleteDocumentSegment(String datasetId, String documentId, String segmentId) throws IOException {

        difyClient.deleteDocumentSegment(datasetId, documentId, segmentId);

    }

    @Override
    public JSONObject getUploadedFileInfo(String datasetId, String documentId) throws IOException {

       return difyClient.getUploadedFileInfo(datasetId, documentId);


    }

    @Override
    public JSONObject updateDocumentSegment(
            String datasetId,
            String documentId,
            String segmentId,
            String content,
            String answer,
            List<String> keywords,
            Boolean enabled
    ) throws IOException {

        String url = difyBaseUrl + "/v1/datasets/" + datasetId + "/documents/" + documentId + "/segments/" + segmentId;

        CloseableHttpClient httpClient = HttpClients.createDefault();
        try {
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Authorization", "Bearer " + difyApiKey);
            httpPost.setHeader("Content-Type", "application/json");

            // 构建 JSON 请求体
            JSONObject segment = new JSONObject();
            segment.put("content", content);
            if (answer != null) {
                segment.put("answer", answer);
            }
            if (keywords != null) {
                segment.put("keywords", keywords);
            }
            if (enabled != null) {
                segment.put("enabled", enabled);
            }

            JSONObject requestBody = new JSONObject();
            requestBody.put("segment", segment);

            httpPost.setEntity(new StringEntity(requestBody.toJSONString(), StandardCharsets.UTF_8));

            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                int statusCode = response.getStatusLine().getStatusCode();
                String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);

                if (statusCode != 200) {
                    throw new IOException("更新分段失败，状态码：" + statusCode + "，响应内容：" + responseBody);
                }

                return JSONObject.parseObject(responseBody);
            }
        } finally {
            httpClient.close();
        }
    }

    @Override
    public JSONObject retrieveFromDataset(ChatKnowledgeDatasetSaveReqVO createReqVO) throws IOException {
        // 构建请求体
        JSONObject retrievalModel = new JSONObject();
        retrievalModel.put("search_method", "keyword_search"); // 或 "semantic_search", "hybrid_search"
        retrievalModel.put("reranking_enable", false);
        retrievalModel.put("top_k", 5);
        retrievalModel.put("score_threshold_enabled", false);

        JSONObject requestBody = new JSONObject();
        requestBody.put("query", createReqVO.getQuery());
        requestBody.put("retrieval_model", retrievalModel);
       return difyClient.retrieveFromDataset(createReqVO.getDifyDatasetId(),  createReqVO.getQuery(), retrievalModel);


    }


}