package tech.easyflow.chatlog.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Service;
import tech.easyflow.chatlog.cache.ChatHotStateService;
import tech.easyflow.chatlog.domain.command.ChatAppendMessageCommand;
import tech.easyflow.chatlog.domain.command.ChatRoundSelectCommand;
import tech.easyflow.chatlog.domain.command.ChatRoundUpsertCommand;
import tech.easyflow.chatlog.domain.command.ChatSessionUpsertCommand;
import tech.easyflow.chatlog.domain.dto.ChatRoundRecord;
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
import tech.easyflow.chatlog.domain.event.ChatPersistEvent;
import tech.easyflow.chatlog.domain.event.ChatPersistEventType;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionDeletePayload;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionRenamePayload;
import tech.easyflow.chatlog.support.ChatJsonSupport;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.math.BigInteger;
import java.util.Date;
import java.util.UUID;

@Service
public class ChatPersistDispatcher {

    private static final Logger log = LoggerFactory.getLogger(ChatPersistDispatcher.class);

    private final ChatHotStateService chatHotStateService;
    private final ChatPersistEventProducer eventProducer;
    private final ChatPersistMySqlApplyService mySqlApplyService;
    private final ChatJsonSupport chatJsonSupport;

    public ChatPersistDispatcher(ChatHotStateService chatHotStateService,
                                 ChatPersistEventProducer eventProducer,
                                 ChatPersistMySqlApplyService mySqlApplyService,
                                 ChatJsonSupport chatJsonSupport) {
        this.chatHotStateService = chatHotStateService;
        this.eventProducer = eventProducer;
        this.mySqlApplyService = mySqlApplyService;
        this.chatJsonSupport = chatJsonSupport;
    }

    public ChatSessionSummary createOrTouchSession(ChatSessionUpsertCommand command) {
        ChatSessionSummary summary = chatHotStateService.touchSession(command);
        ChatPersistEvent event = buildEvent(
                UUID.randomUUID().toString(),
                ChatPersistEventType.SESSION_PREPARED,
                command.getSessionId(),
                command.getUserId(),
                command.getAssistantId(),
                command.getOperateAt(),
                chatJsonSupport.toJson(command)
        );
        persistImmediately(event);
        eventProducer.send(event);
        return summary;
    }

    public ChatRoundRecord createOrTouchRound(ChatRoundUpsertCommand command) {
        ChatRoundRecord record = chatHotStateService.createOrTouchRound(command);
        ChatPersistEvent event = buildEvent(
                UUID.randomUUID().toString(),
                ChatPersistEventType.ROUND_UPSERTED,
                command.getSessionId(),
                BigInteger.ZERO,
                BigInteger.ZERO,
                command.getOperateAt(),
                chatJsonSupport.toJson(command)
        );
        persistImmediately(event);
        eventProducer.send(event);
        return record;
    }

    public void selectRoundVariant(ChatRoundSelectCommand command) {
        chatHotStateService.selectVariant(command);
        ChatPersistEvent event = buildEvent(
                UUID.randomUUID().toString(),
                ChatPersistEventType.ROUND_VARIANT_SELECTED,
                command.getSessionId(),
                BigInteger.ZERO,
                BigInteger.ZERO,
                command.getOperateAt(),
                chatJsonSupport.toJson(command)
        );
        persistImmediately(event);
        eventProducer.send(event);
    }

    public void appendUserMessage(ChatAppendMessageCommand command) {
        appendMessage(command, ChatPersistEventType.USER_MESSAGE_APPENDED);
    }

    public void appendAssistantMessage(ChatAppendMessageCommand command) {
        appendMessage(command, ChatPersistEventType.ASSISTANT_MESSAGE_APPENDED);
    }

    public void renameSession(BigInteger sessionId, BigInteger userId, String title, BigInteger operatorId) {
        Date operateAt = new Date();
        chatHotStateService.renameSession(sessionId, userId, title, operatorId, operateAt);

        ChatSessionRenamePayload payload = new ChatSessionRenamePayload();
        payload.setSessionId(sessionId);
        payload.setUserId(userId);
        payload.setTitle(title);
        payload.setOperatorId(operatorId);
        payload.setOperateAt(operateAt);
        ChatPersistEvent event = buildEvent(
                UUID.randomUUID().toString(),
                ChatPersistEventType.SESSION_RENAMED,
                sessionId,
                userId,
                BigInteger.ZERO,
                operateAt,
                chatJsonSupport.toJson(payload)
        );
        persistImmediately(event);
        eventProducer.send(event);
    }

    public void deleteSession(BigInteger sessionId, BigInteger userId, BigInteger operatorId) {
        Date operateAt = new Date();
        chatHotStateService.deleteSession(sessionId, userId, operatorId, operateAt);

        ChatSessionDeletePayload payload = new ChatSessionDeletePayload();
        payload.setSessionId(sessionId);
        payload.setUserId(userId);
        payload.setOperatorId(operatorId);
        payload.setOperateAt(operateAt);
        eventProducer.send(buildEvent(
                UUID.randomUUID().toString(),
                ChatPersistEventType.SESSION_DELETED,
                sessionId,
                userId,
                BigInteger.ZERO,
                operateAt,
                chatJsonSupport.toJson(payload)
        ));
    }

    private void appendMessage(ChatAppendMessageCommand command, ChatPersistEventType eventType) {
        chatHotStateService.appendMessage(command);
        ChatPersistEvent event = buildEvent(
                eventId("message", command.getMessageId()),
                eventType,
                command.getSessionId(),
                command.getUserId(),
                command.getAssistantId(),
                command.getCreated(),
                chatJsonSupport.toJson(command)
        );
        persistImmediately(event);
        eventProducer.send(event);
    }

    /**
     * 先同步写入 MySQL，再发送异步事件，保证会话列表和版本切换读取有确定来源。
     *
     * @param event 持久化事件
     */
    private void persistImmediately(ChatPersistEvent event) {
        try {
            mySqlApplyService.apply(java.util.List.of(event));
        } catch (RuntimeException ex) {
            log.error("聊天记录同步写入 MySQL 失败，eventId={}, eventType={}, sessionId={}",
                    event == null ? null : event.getEventId(),
                    event == null ? null : event.getEventType(),
                    event == null ? null : event.getSessionId(),
                    ex);
            throw new BusinessException("聊天记录持久化失败，请稍后重试");
        }
    }

    private ChatPersistEvent buildEvent(String eventId,
                                        ChatPersistEventType eventType,
                                        BigInteger sessionId,
                                        BigInteger userId,
                                        BigInteger assistantId,
                                        Date occurredAt,
                                        String payload) {
        ChatPersistEvent event = new ChatPersistEvent();
        event.setEventId(eventId);
        event.setEventType(eventType);
        event.setSessionId(sessionId);
        event.setUserId(userId);
        event.setAssistantId(assistantId);
        event.setOccurredAt(occurredAt == null ? new Date() : occurredAt);
        event.setTraceId(resolveTraceId(eventId));
        event.setPayload(payload);
        return event;
    }

    private String resolveTraceId(String fallback) {
        String traceId = MDC.get("traceId");
        if (traceId == null || traceId.isBlank()) {
            return fallback;
        }
        return traceId;
    }

    private String eventId(String prefix, BigInteger id) {
        return prefix + "-" + (id == null ? UUID.randomUUID() : id);
    }
}
