package tech.easyflow.chatlog.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.chatlog.domain.command.*;
import tech.easyflow.chatlog.domain.event.ChatPersistEvent;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionDeletePayload;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionRenamePayload;
import tech.easyflow.chatlog.repository.mysql.MySqlChatLogRepository;
import tech.easyflow.chatlog.repository.mysql.MySqlChatLogTableManager;
import tech.easyflow.chatlog.repository.mysql.MySqlChatRoundRepository;
import tech.easyflow.chatlog.repository.mysql.MySqlChatSessionRepository;
import tech.easyflow.chatlog.support.ChatConstants;
import tech.easyflow.chatlog.support.ChatJsonSupport;

import java.math.BigInteger;
import java.time.YearMonth;
import java.time.ZoneId;
import java.util.*;

@Service
public class ChatPersistMySqlApplyService {

    private final MySqlChatSessionRepository sessionRepository;
    private final MySqlChatLogRepository logRepository;
    private final MySqlChatRoundRepository roundRepository;
    private final MySqlChatLogTableManager tableManager;
    private final ChatJsonSupport chatJsonSupport;

    public ChatPersistMySqlApplyService(MySqlChatSessionRepository sessionRepository,
                                        MySqlChatLogRepository logRepository,
                                        MySqlChatRoundRepository roundRepository,
                                        MySqlChatLogTableManager tableManager,
                                        ChatJsonSupport chatJsonSupport) {
        this.sessionRepository = sessionRepository;
        this.logRepository = logRepository;
        this.roundRepository = roundRepository;
        this.tableManager = tableManager;
        this.chatJsonSupport = chatJsonSupport;
    }

    @Transactional(rollbackFor = Exception.class)
    public void apply(List<ChatPersistEvent> events) {
        if (events == null || events.isEmpty()) {
            return;
        }

        Map<BigInteger, ChatSessionUpsertCommand> sessionUpserts = new LinkedHashMap<>();
        Map<BigInteger, ChatRoundUpsertCommand> roundUpserts = new LinkedHashMap<>();
        List<ChatRoundSelectCommand> roundSelections = new ArrayList<>();
        List<ChatAppendMessageCommand> appendCommands = new ArrayList<>();
        Map<BigInteger, ChatSessionSummaryCommand> summaryCommands = new LinkedHashMap<>();
        List<ChatSessionRenamePayload> renamePayloads = new ArrayList<>();
        List<ChatSessionDeletePayload> deletePayloads = new ArrayList<>();
        Set<YearMonth> months = new LinkedHashSet<>();

        for (ChatPersistEvent event : events) {
            if (event == null || event.getEventType() == null) {
                continue;
            }
            switch (event.getEventType()) {
                case SESSION_PREPARED -> {
                    ChatSessionUpsertCommand command = chatJsonSupport.fromJson(event.getPayload(), ChatSessionUpsertCommand.class);
                    if (command != null && command.getSessionId() != null) {
                        sessionUpserts.put(command.getSessionId(), command);
                    }
                }
                case ROUND_UPSERTED -> {
                    ChatRoundUpsertCommand command = chatJsonSupport.fromJson(event.getPayload(), ChatRoundUpsertCommand.class);
                    if (command != null && command.getRoundId() != null) {
                        roundUpserts.put(command.getRoundId(), command);
                    }
                }
                case ROUND_VARIANT_SELECTED -> {
                    ChatRoundSelectCommand command = chatJsonSupport.fromJson(event.getPayload(), ChatRoundSelectCommand.class);
                    if (command != null && command.getRoundId() != null) {
                        roundSelections.add(command);
                    }
                }
                case USER_MESSAGE_APPENDED, ASSISTANT_MESSAGE_APPENDED -> {
                    ChatAppendMessageCommand command = chatJsonSupport.fromJson(event.getPayload(), ChatAppendMessageCommand.class);
                    if (command == null || command.getSessionId() == null || command.getMessageId() == null) {
                        continue;
                    }
                    appendCommands.add(command);
                    months.add(resolveMonth(command.getCreated()));
                    accumulateSummary(summaryCommands, command);
                }
                case SESSION_RENAMED -> {
                    ChatSessionRenamePayload payload = chatJsonSupport.fromJson(event.getPayload(), ChatSessionRenamePayload.class);
                    if (payload != null && payload.getSessionId() != null) {
                        renamePayloads.add(payload);
                    }
                }
                case SESSION_DELETED -> {
                    ChatSessionDeletePayload payload = chatJsonSupport.fromJson(event.getPayload(), ChatSessionDeletePayload.class);
                    if (payload != null && payload.getSessionId() != null) {
                        deletePayloads.add(payload);
                    }
                }
                default -> {
                }
            }
        }

        if (!sessionUpserts.isEmpty()) {
            sessionRepository.createOrTouchBatch(new ArrayList<>(sessionUpserts.values()));
        }
        if (!roundUpserts.isEmpty()) {
            roundRepository.createOrTouchBatch(new ArrayList<>(roundUpserts.values()));
        }
        if (!months.isEmpty()) {
            for (YearMonth month : months) {
                tableManager.ensureMonthTable(month);
            }
        }
        List<ChatAppendMessageCommand> insertedCommands = List.of();
        if (!appendCommands.isEmpty()) {
            insertedCommands = logRepository.appendMessages(appendCommands);
        }
        if (!insertedCommands.isEmpty()) {
            sessionRepository.createOrTouchBatch(buildSessionUpserts(insertedCommands));
            summaryCommands.clear();
            for (ChatAppendMessageCommand insertedCommand : insertedCommands) {
                accumulateSummary(summaryCommands, insertedCommand);
            }
            sessionRepository.updateSummaries(new ArrayList<>(summaryCommands.values()));
        }
        if (!roundSelections.isEmpty()) {
            roundRepository.selectVariants(roundSelections);
        }
        if (!renamePayloads.isEmpty()) {
            sessionRepository.renameSessions(renamePayloads);
        }
        if (!deletePayloads.isEmpty()) {
            sessionRepository.deleteSessions(deletePayloads);
        }
    }

    private void accumulateSummary(Map<BigInteger, ChatSessionSummaryCommand> summaryCommands,
                                   ChatAppendMessageCommand command) {
        ChatSessionSummaryCommand summary = summaryCommands.computeIfAbsent(command.getSessionId(), key -> {
            ChatSessionSummaryCommand created = new ChatSessionSummaryCommand();
            created.setSessionId(command.getSessionId());
            created.setUserId(command.getUserId());
            created.setLastMessageAt(null);
            created.setAccessAt(null);
            created.setModifiedAt(null);
            created.setMessageIncrement(0);
            return created;
        });
        summary.setMessageIncrement(summary.getMessageIncrement() + 1);
        if (ChatConstants.MESSAGE_KIND_ASSISTANT_VARIANT.equals(command.getMessageKind())
                && command.getVariantIndex() != null
                && command.getVariantIndex() > 1) {
            summary.setMessageIncrement(Math.max(summary.getMessageIncrement() - 1, 0));
        }
        Date commandCreated = defaultDate(command.getCreated());
        if (summary.getLastMessageAt() == null || !commandCreated.before(summary.getLastMessageAt())) {
            summary.setLastSenderId(command.getSenderId());
            summary.setLastSenderName(command.getSenderName());
            summary.setLastMessagePreview(trimPreview(command.getContentText()));
            summary.setLastMessageAt(commandCreated);
            summary.setAccessAt(commandCreated);
            summary.setModifiedAt(commandCreated);
            summary.setOperatorId(command.getCreatedBy());
        }
    }

    List<ChatSessionUpsertCommand> buildSessionUpserts(List<ChatAppendMessageCommand> commands) {
        if (commands == null || commands.isEmpty()) {
            return List.of();
        }
        Map<BigInteger, ChatSessionUpsertCommand> upserts = new LinkedHashMap<>();
        for (ChatAppendMessageCommand command : commands) {
            if (command == null || command.getSessionId() == null) {
                continue;
            }
            ChatSessionUpsertCommand upsert = upserts.computeIfAbsent(command.getSessionId(), key -> {
                ChatSessionUpsertCommand created = new ChatSessionUpsertCommand();
                created.setSessionId(command.getSessionId());
                created.setTenantId(command.getTenantId());
                created.setDeptId(command.getDeptId());
                created.setUserId(command.getUserId());
                created.setAssistantId(command.getAssistantId());
                created.setOperateAt(null);
                return created;
            });
            if (upsert.getTenantId() == null) {
                upsert.setTenantId(command.getTenantId());
            }
            if (upsert.getDeptId() == null) {
                upsert.setDeptId(command.getDeptId());
            }
            if (upsert.getUserId() == null) {
                upsert.setUserId(command.getUserId());
            }
            if (upsert.getAssistantId() == null) {
                upsert.setAssistantId(command.getAssistantId());
            }
            if (isBlank(upsert.getUserAccount()) && !isBlank(command.getUserAccount())) {
                upsert.setUserAccount(command.getUserAccount().trim());
            }
            if (isBlank(upsert.getAssistantCode()) && !isBlank(command.getAssistantCode())) {
                upsert.setAssistantCode(command.getAssistantCode().trim());
            }
            if (isBlank(upsert.getAssistantName()) && !isBlank(command.getAssistantName())) {
                upsert.setAssistantName(command.getAssistantName().trim());
            }
            if (isBlank(upsert.getTitle()) && !isBlank(command.getSessionTitle())) {
                upsert.setTitle(command.getSessionTitle().trim());
            }
            Date operateAt = defaultDate(command.getCreated());
            if (upsert.getOperateAt() == null || !operateAt.before(upsert.getOperateAt())) {
                upsert.setOperateAt(operateAt);
                upsert.setOperatorId(command.getCreatedBy());
            }
        }
        for (ChatSessionUpsertCommand upsert : upserts.values()) {
            if (isBlank(upsert.getUserAccount())) {
                upsert.setUserAccount("");
            }
            if (upsert.getOperateAt() == null) {
                upsert.setOperateAt(new Date());
            }
            if (upsert.getOperatorId() == null) {
                upsert.setOperatorId(upsert.getUserId());
            }
        }
        return new ArrayList<>(upserts.values());
    }

    private YearMonth resolveMonth(Date createdAt) {
        return YearMonth.from(defaultDate(createdAt).toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
    }

    private String trimPreview(String text) {
        if (text == null) {
            return "";
        }
        return text.length() <= 200 ? text : text.substring(0, 200);
    }

    private boolean isBlank(String value) {
        return value == null || value.isBlank();
    }

    private Date defaultDate(Date value) {
        return value == null ? new Date() : value;
    }
}
