package org.jeecg.modules.adweb.gpt.service.impl;

import com.google.common.collect.Lists;
import com.unfbx.chatgpt.OpenAiStreamClient;
import com.unfbx.chatgpt.entity.chat.ChatCompletion;
import com.unfbx.chatgpt.entity.chat.Message;
import com.unfbx.chatgpt.exception.BaseException;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.SpringContextUtils;
import org.jeecg.common.util.UUIDGenerator;
import org.jeecg.modules.adweb.common.util.AdwebRedisUtil;
import org.jeecg.modules.adweb.gpt.cache.SseEmitterCache;
import org.jeecg.modules.adweb.gpt.entity.ChatHistory;
import org.jeecg.modules.adweb.gpt.listeners.OpenAISSEEventSourceListener;
import org.jeecg.modules.adweb.gpt.service.ChatService;
import org.jeecg.modules.adweb.gpt.service.IChatHistoryService;
import org.jeecg.modules.adweb.gpt.vo.ChatHistoryVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * AI聊天助手Service
 *
 * @author chenrui
 * @date 2024/1/26 20:07
 */
@Slf4j
@Service
public class ChatServiceImpl implements ChatService {

    // private static final String CACHE_KEY_PREFIX = "ai:chat:";
    // private static final String CACHE_KEY_MSG_HISTORY = "msg_history";

    private static final String REDIS_KEY_PREFIX_TOPIC_CONTEXT = "ai:chat:topic-context";

    @Autowired AdwebRedisUtil adwebRedisUtil;

    @Autowired private OpenAiStreamClient openAiStreamClient;

    @Autowired private IChatHistoryService chatHistoryService;

    /** 防止client不能成功注入 */
    private OpenAiStreamClient ensureClient() {
        if (Objects.isNull(this.openAiStreamClient)) {
            this.openAiStreamClient = SpringContextUtils.getBean(OpenAiStreamClient.class);
        }
        return this.openAiStreamClient;
    }

    @Override
    public SseEmitter createChat() {
        String uid = this.getUserId();
        // 默认30秒超时,设置为0L则永不超时
        SseEmitter sseEmitter = new SseEmitter(-0L);
        // 完成后回调
        sseEmitter.onCompletion(
                () -> {
                    log.info("[{}]结束连接...................", uid);
                    SseEmitterCache.CACHE.remove(uid);
                });
        // 超时回调
        sseEmitter.onTimeout(
                () -> {
                    log.info("[{}]连接超时...................", uid);
                });
        // 异常回调
        sseEmitter.onError(
                throwable -> {
                    try {
                        log.info("[{}]连接异常,{}", uid, throwable.toString());
                        sseEmitter.send(
                                SseEmitter.event()
                                        .id(uid)
                                        .name("发生异常！")
                                        .data(Message.builder().content("发生异常请重试！").build())
                                        .reconnectTime(3000));
                        SseEmitterCache.CACHE.put(uid, sseEmitter);
                    } catch (IOException e) {
                        log.error(e.getMessage(), e);
                    }
                });
        try {
            sseEmitter.send(SseEmitter.event().reconnectTime(5000));
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        SseEmitterCache.CACHE.put(uid, sseEmitter);
        log.info("[{}]创建sse连接成功！", uid);
        return sseEmitter;
    }

    @Override
    public void closeChat() {
        String uid = this.getUserId();
        SseEmitter sse = SseEmitterCache.CACHE.get(uid);
        if (Objects.nonNull(sse)) {
            sse.complete();
            // 移除
            SseEmitterCache.CACHE.remove(uid);
        }
    }

    @Override
    public void sendMessage(String topicId, String message) {
        String uid = this.getUserId();
        if (StringUtils.isBlank(message)) {
            log.info("参数异常，message为null");
            throw new BaseException("参数异常，message不能为空~");
        }
        if (StringUtils.isBlank(topicId)) {
            topicId = UUIDGenerator.generate();
        }

        log.info("话题id:{}", topicId);
        // 1. 获取当前话题的context - 最近100条用户message
        String contextKey = String.format("%s:%s:%s", REDIS_KEY_PREFIX_TOPIC_CONTEXT, uid, topicId);
        List<Message> topicContext =
                Optional.ofNullable(adwebRedisUtil.lGet(contextKey, 0, -1))
                        .orElse(Lists.newArrayList())
                        .stream()
                        .map(Message.class::cast)
                        .collect(Collectors.toList());
        // 1.1 添加当前message到context
        topicContext.add(Message.builder().content(message).role(Message.Role.USER).build());

        // 2. 发送GPT请求
        SseEmitter sseEmitter = SseEmitterCache.CACHE.get(uid);
        if (Objects.isNull(sseEmitter)) {
            log.info("聊天消息推送失败uid:[{}]，没有创建连接，请重试。", uid);
            throw new JeecgBootException("聊天消息推送失败uid：[{}]，没有创建连接，请重试~");
        }
        OpenAISSEEventSourceListener openAIEventSourceListener =
                new OpenAISSEEventSourceListener(topicId, sseEmitter);
        ChatCompletion completion =
                ChatCompletion.builder()
                        .messages(topicContext)
                        .model(ChatCompletion.Model.GPT_3_5_TURBO.getName())
                        .build();
        ensureClient().streamChatCompletion(completion, openAIEventSourceListener);

        // 3. 将当前话题的context保存到Redis
        adwebRedisUtil.del(contextKey);
        adwebRedisUtil.lSet(
                contextKey,
                topicContext.stream()
                        // 每个话题仅保存最后100条message
                        .skip(Math.max(0, topicContext.size() - 100))
                        .collect(Collectors.toList()),
                // 话题context TTL设置为3小时
                3 * 60 * 60);

        Result.ok(completion.tokens());
    }

    @Override
    public void saveHistory(ChatHistoryVO chatHistoryVO) {
        // String uid = getUserId();
        // String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
        // redisTemplate.opsForValue().set(cacheKey, chatHistoryVO.getContent());
        // return Result.OK("保存成功");

        // AdWeb重构 - 聊天记录按周保存到DB，不存Redis
        ChatHistory chatHistory = chatHistoryService.getChatHistoryOfWeek(this.getUserId());
        chatHistory.setContent(chatHistoryVO.getContent());
        chatHistoryService.updateById(chatHistory);
    }

    @Override
    public ChatHistoryVO getHistoryByTopic() {
        // String uid = getUserId();
        // String cacheKey = CACHE_KEY_PREFIX + CACHE_KEY_MSG_HISTORY + ":" + uid;
        // String historyContent = (String) redisTemplate.opsForValue().get(cacheKey);
        // ChatHistoryVO chatHistoryVO = new ChatHistoryVO();
        // chatHistoryVO.setContent(historyContent);
        // return Result.OK(chatHistoryVO);

        // AdWeb重构 - 聊天记录按周保存到DB，不存Redis
        ChatHistory chatHistory = chatHistoryService.getChatHistoryOfWeek(this.getUserId());

        ChatHistoryVO chatHistoryVO = new ChatHistoryVO();
        chatHistoryVO.setContent(Objects.nonNull(chatHistory) ? chatHistory.getContent() : null);

        return chatHistoryVO;
    }

    /** 获取当前登陆用户ID */
    private String getUserId() {
        LoginUser sysUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        return sysUser.getId();
    }
}
