package tech.easyflow.chatlog.service.impl;

import org.springframework.stereotype.Service;
import tech.easyflow.chatlog.config.ChatSyncProperties;
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
import tech.easyflow.chatlog.domain.dto.ChatSyncResult;
import tech.easyflow.chatlog.domain.entity.ChatSyncCheckpoint;
import tech.easyflow.chatlog.repository.analyticaldb.ChatAnalyticalDBRepository;
import tech.easyflow.chatlog.repository.mysql.MySqlChatLogRepository;
import tech.easyflow.chatlog.repository.mysql.MySqlChatLogTableManager;
import tech.easyflow.chatlog.repository.mysql.MySqlChatSessionRepository;
import tech.easyflow.chatlog.service.ChatSyncCheckpointService;
import tech.easyflow.chatlog.service.ChatSyncService;
import tech.easyflow.chatlog.support.ChatConstants;

import java.math.BigInteger;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

@Service
public class ChatSyncServiceImpl implements ChatSyncService {

    private final MySqlChatSessionRepository sessionRepository;
    private final MySqlChatLogRepository logRepository;
    private final MySqlChatLogTableManager tableManager;
    private final ChatAnalyticalDBRepository analyticalDBRepository;
    private final ChatSyncCheckpointService checkpointService;
    private final ChatSyncProperties syncProperties;

    public ChatSyncServiceImpl(MySqlChatSessionRepository sessionRepository,
                               MySqlChatLogRepository logRepository,
                               MySqlChatLogTableManager tableManager,
                               ChatAnalyticalDBRepository analyticalDBRepository,
                               ChatSyncCheckpointService checkpointService,
                               ChatSyncProperties syncProperties) {
        this.sessionRepository = sessionRepository;
        this.logRepository = logRepository;
        this.tableManager = tableManager;
        this.analyticalDBRepository = analyticalDBRepository;
        this.checkpointService = checkpointService;
        this.syncProperties = syncProperties;
    }

    @Override
    public ChatSyncResult syncSessions() {
        ChatSyncResult result = new ChatSyncResult();
        result.setSyncCode(ChatConstants.CHECKPOINT_SYNC_CODE_SESSION);
        if (!analyticalDBRepository.enabled()) {
            return result;
        }
        int totalRows = 0;
        ChatSyncCheckpoint checkpoint = checkpointService.getOrCreate(ChatConstants.CHECKPOINT_SYNC_CODE_SESSION, "default");
        List<ChatSessionSummary> rows = sessionRepository.loadModifiedAfter(
                checkpoint.getCursorTime(),
                checkpoint.getCursorId(),
                syncProperties.getBatchSize()
        );
        if (!rows.isEmpty()) {
            analyticalDBRepository.upsertSessions(rows);
            ChatSessionSummary last = rows.get(rows.size() - 1);
            checkpoint.setCursorTime(last.getModified());
            checkpoint.setCursorId(last.getId());
            checkpoint.setCursorTable(ChatConstants.SESSION_TABLE);
            checkpoint.setLastBatchSize(rows.size());
            checkpoint.setLastSuccessTime(new Date());
            checkpoint.setStatus("SUCCESS");
            checkpoint.setModified(new Date());
            checkpointService.updateById(checkpoint);
            totalRows += rows.size();
        }
        result.setSyncedRows(totalRows);
        return result;
    }

    @Override
    public ChatSyncResult syncLogs() {
        ChatSyncResult result = new ChatSyncResult();
        result.setSyncCode(ChatConstants.CHECKPOINT_SYNC_CODE_LOG);
        if (!analyticalDBRepository.enabled()) {
            return result;
        }
        ChatSyncCheckpoint checkpoint = checkpointService.getOrCreate(ChatConstants.CHECKPOINT_SYNC_CODE_LOG, "default");
        List<YearMonth> months = tableManager.listRecentExistingMonths(syncProperties.getRetentionMonths());
        Set<LocalDate> touchedDates = new LinkedHashSet<>();
        int totalRows = 0;
        for (YearMonth month : months) {
            String table = "chat_log_" + month.toString().replace("-", "");
            if (checkpoint.getCursorTable() != null && checkpoint.getCursorTable().compareTo(table) > 0) {
                continue;
            }
            List<ChatMessageRecord> rows = logRepository.loadIncremental(
                    table,
                    table.equals(checkpoint.getCursorTable()) ? checkpoint.getCursorTime() : null,
                    table.equals(checkpoint.getCursorTable()) ? checkpoint.getCursorId() : BigInteger.ZERO,
                    syncProperties.getBatchSize()
            );
            if (rows.isEmpty()) {
                continue;
            }
            analyticalDBRepository.appendLogs(rows);
            ChatMessageRecord last = rows.get(rows.size() - 1);
            checkpoint.setCursorTable(table);
            checkpoint.setCursorTime(last.getCreated());
            checkpoint.setCursorId(last.getId());
            checkpoint.setLastBatchSize(rows.size());
            checkpoint.setLastSuccessTime(new Date());
            checkpoint.setStatus("SUCCESS");
            checkpoint.setModified(new Date());
            checkpointService.updateById(checkpoint);
            totalRows += rows.size();
            rows.stream()
                    .map(item -> item.getCreated().toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
                    .forEach(touchedDates::add);
        }
        analyticalDBRepository.refreshDws(touchedDates);
        result.setSyncedRows(totalRows);
        result.setTouchedDates(new ArrayList<>(touchedDates.stream().map(LocalDate::toString).toList()));
        return result;
    }

    @Override
    public ChatSyncResult repairLogs() {
        ChatSyncResult result = new ChatSyncResult();
        result.setSyncCode("chat_log_repair");
        if (!analyticalDBRepository.enabled()) {
            return result;
        }
        Date startTime = Date.from(LocalDate.now()
                .minusDays(syncProperties.getRepairLookbackDays())
                .atStartOfDay(ZoneId.systemDefault())
                .toInstant());
        List<ChatMessageRecord> rows = logRepository.loadRepairRows(
                tableManager.listRecentExistingMonths(syncProperties.getRetentionMonths()),
                startTime
        );
        analyticalDBRepository.appendLogs(rows);
        Set<LocalDate> dates = new LinkedHashSet<>();
        rows.stream().map(item -> item.getCreated().toInstant().atZone(ZoneId.systemDefault()).toLocalDate())
                .forEach(dates::add);
        analyticalDBRepository.refreshDws(dates);
        result.setSyncedRows(rows.size());
        result.setTouchedDates(new ArrayList<>(dates.stream().map(LocalDate::toString).toList()));
        return result;
    }

    @Override
    public void maintainMysqlTables() {
        tableManager.ensureCurrentAndNextMonth();
        clearExpiredSessions();
        if (!analyticalDBRepository.enabled()) {
            return;
        }
        ChatSyncCheckpoint checkpoint = checkpointService.getOrCreate(ChatConstants.CHECKPOINT_SYNC_CODE_LOG, "default");
        YearMonth threshold = YearMonth.now().minusMonths(syncProperties.getRetentionMonths());
        for (int i = syncProperties.getRetentionMonths() + 1; i <= 24; i++) {
            YearMonth month = YearMonth.now().minusMonths(i);
            if (checkpoint.getCursorTable() != null && checkpoint.getCursorTable().compareTo("chat_log_" + month.toString().replace("-", "")) <= 0) {
                continue;
            }
            if (month.isBefore(threshold) && tableManager.tableExists("chat_log_" + month.toString().replace("-", ""))) {
                tableManager.dropMonthTable(month);
            }
        }
    }

    @Override
    public void startupCheck() {
        tableManager.ensureCurrentAndNextMonth();
    }

    private void clearExpiredSessions() {
        Date expireBefore = Date.from(LocalDate.now()
                .minusMonths(syncProperties.getRetentionMonths())
                .atStartOfDay(ZoneId.systemDefault())
                .toInstant());
        while (sessionRepository.deleteExpiredSessions(expireBefore, syncProperties.getBatchSize()) > 0) {
            // loop until all expired session hot data is purged
        }
    }
}
