package tech.easyflow.chatlog.repository.mysql;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import tech.easyflow.chatlog.domain.command.ChatAppendMessageCommand;
import tech.easyflow.chatlog.domain.dto.ChatMessageRecord;
import tech.easyflow.chatlog.support.ChatJsonSupport;
import tech.easyflow.chatlog.support.ChatConstants;
import tech.easyflow.chatlog.support.ChatTableRouter;

import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Repository
public class MySqlChatLogRepository {

    private final JdbcTemplate jdbcTemplate;
    private final ChatTableRouter tableRouter;
    private final ChatJsonSupport jsonSupport;

    public MySqlChatLogRepository(JdbcTemplate jdbcTemplate,
                                  ChatTableRouter tableRouter,
                                  ChatJsonSupport jsonSupport) {
        this.jdbcTemplate = jdbcTemplate;
        this.tableRouter = tableRouter;
        this.jsonSupport = jsonSupport;
    }

    public List<ChatAppendMessageCommand> appendMessages(List<ChatAppendMessageCommand> commands) {
        if (commands == null || commands.isEmpty()) {
            return Collections.emptyList();
        }
        List<ChatAppendMessageCommand> inserted = new ArrayList<>();
        Map<YearMonth, List<ChatAppendMessageCommand>> grouped = new LinkedHashMap<>();
        for (ChatAppendMessageCommand command : commands) {
            YearMonth month = YearMonth.from(command.getCreated().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate());
            grouped.computeIfAbsent(month, key -> new ArrayList<>()).add(command);
        }
        for (Map.Entry<YearMonth, List<ChatAppendMessageCommand>> entry : grouped.entrySet()) {
            String table = tableRouter.resolveLogTable(entry.getKey());
            String sql = "INSERT IGNORE INTO `" + table + "` " +
                    "(id, tenant_id, dept_id, session_id, user_id, assistant_id, round_id, round_no, sender_id, sender_name, sender_role, message_kind, variant_index, content_type, content_text, content_payload, created, created_by, sync_version) " +
                    "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
            int[] results = jdbcTemplate.batchUpdate(sql, new org.springframework.jdbc.core.BatchPreparedStatementSetter() {
                @Override
                public void setValues(java.sql.PreparedStatement ps, int i) throws java.sql.SQLException {
                    ChatAppendMessageCommand command = entry.getValue().get(i);
                    Timestamp created = new Timestamp(command.getCreated().getTime());
                    ps.setObject(1, command.getMessageId());
                    ps.setObject(2, command.getTenantId());
                    ps.setObject(3, command.getDeptId());
                    ps.setObject(4, command.getSessionId());
                    ps.setObject(5, command.getUserId());
                    ps.setObject(6, command.getAssistantId());
                    ps.setObject(7, command.getRoundId());
                    ps.setObject(8, command.getRoundNo());
                    ps.setObject(9, command.getSenderId());
                    ps.setString(10, command.getSenderName());
                    ps.setString(11, command.getSenderRole());
                    ps.setString(12, command.getMessageKind());
                    ps.setObject(13, command.getVariantIndex());
                    ps.setString(14, command.getContentType());
                    ps.setString(15, command.getContentText());
                    ps.setString(16, jsonSupport.toJson(command.getContentPayload()));
                    ps.setTimestamp(17, created);
                    ps.setObject(18, command.getCreatedBy());
                    ps.setLong(19, command.getCreated().getTime());
                }

                @Override
                public int getBatchSize() {
                    return entry.getValue().size();
                }
            });
            for (int i = 0; i < results.length; i++) {
                if (results[i] != 0 && results[i] != Statement.EXECUTE_FAILED) {
                    inserted.add(entry.getValue().get(i));
                }
            }
        }
        return inserted;
    }

    public List<ChatMessageRecord> listRecentTail(BigInteger sessionId, List<YearMonth> months, int limit) {
        List<ChatMessageRecord> records = new ArrayList<>();
        for (YearMonth month : months) {
            String table = tableRouter.resolveLogTable(month);
            List<ChatMessageRecord> current = jdbcTemplate.query(
                    "SELECT l.*, r.round_no AS joined_round_no, r.variant_count, r.selected_variant_index, " +
                            "CASE WHEN r.status IS NOT NULL AND r.status <> 'LOCKED' " +
                            "AND NOT EXISTS (SELECT 1 FROM `" + ChatConstants.ROUND_TABLE + "` newer WHERE newer.session_id = r.session_id AND newer.round_no > r.round_no) " +
                            "THEN 1 ELSE 0 END AS switchable " +
                            "FROM `" + table + "` l " +
                            "LEFT JOIN `" + ChatConstants.ROUND_TABLE + "` r ON l.round_id = r.id " +
                            "WHERE l.session_id=? AND (l.round_id IS NULL OR r.id IS NULL OR l.id = r.user_message_id OR l.id = r.selected_assistant_message_id) " +
                            "ORDER BY l.created DESC, l.id DESC LIMIT ?",
                    (rs, rowNum) -> mapRow(rs),
                    sessionId,
                    limit
            );
            records.addAll(current);
            if (records.size() >= limit) {
                break;
            }
        }
        return records.stream()
                .sorted((a, b) -> {
                    int compare = b.getCreated().compareTo(a.getCreated());
                    if (compare != 0) {
                        return compare;
                    }
                    return b.getId().compareTo(a.getId());
                })
                .limit(limit)
                .collect(Collectors.toList());
    }

    /**
     * 从 MySQL 热表分页查询主线可见消息。
     *
     * @param sessionId 会话 ID
     * @param months 查询月份
     * @param offset 分页偏移
     * @param limit 分页条数
     * @return 主线消息列表，按 created desc、id desc 排序
     */
    public List<ChatMessageRecord> listMainlineMessages(BigInteger sessionId, List<YearMonth> months, long offset, int limit) {
        if (sessionId == null || months == null || months.isEmpty() || limit <= 0) {
            return Collections.emptyList();
        }
        int candidateLimit = resolveCandidateLimit(offset, limit);
        Map<BigInteger, ChatMessageRecord> recordMap = new LinkedHashMap<>();
        for (YearMonth month : months) {
            String table = tableRouter.resolveLogTable(month);
            List<ChatMessageRecord> current = jdbcTemplate.query(
                    "SELECT l.*, r.round_no AS joined_round_no, r.variant_count, r.selected_variant_index, " +
                            "CASE WHEN r.status IS NOT NULL AND r.status <> 'LOCKED' " +
                            "AND NOT EXISTS (SELECT 1 FROM `" + ChatConstants.ROUND_TABLE + "` newer WHERE newer.session_id = r.session_id AND newer.round_no > r.round_no) " +
                            "THEN 1 ELSE 0 END AS switchable " +
                            "FROM `" + table + "` l " +
                            "LEFT JOIN `" + ChatConstants.ROUND_TABLE + "` r ON l.round_id = r.id " +
                            "WHERE l.session_id=? AND (l.round_id IS NULL OR r.id IS NULL OR l.id = r.user_message_id OR l.id = r.selected_assistant_message_id) " +
                            "ORDER BY l.created DESC, l.id DESC LIMIT ?",
                    (rs, rowNum) -> mapRow(rs),
                    sessionId,
                    candidateLimit
            );
            for (ChatMessageRecord record : current) {
                if (record != null && record.getId() != null) {
                    recordMap.putIfAbsent(record.getId(), record);
                }
            }
        }
        return recordMap.values().stream()
                .sorted((a, b) -> {
                    int compare = b.getCreated().compareTo(a.getCreated());
                    if (compare != 0) {
                        return compare;
                    }
                    return b.getId().compareTo(a.getId());
                })
                .skip(Math.max(offset, 0))
                .limit(limit)
                .collect(Collectors.toList());
    }

    /**
     * 从 MySQL 热表查询全部主线可见消息。
     *
     * @param sessionId 会话 ID
     * @param months 查询月份
     * @return 主线消息列表，按 created asc、id asc 排序
     */
    public List<ChatMessageRecord> listMainlineMessages(BigInteger sessionId, List<YearMonth> months) {
        if (sessionId == null || months == null || months.isEmpty()) {
            return Collections.emptyList();
        }
        Map<BigInteger, ChatMessageRecord> recordMap = new LinkedHashMap<>();
        for (YearMonth month : months) {
            String table = tableRouter.resolveLogTable(month);
            List<ChatMessageRecord> current = jdbcTemplate.query(
                    "SELECT l.*, r.round_no AS joined_round_no, r.variant_count, r.selected_variant_index, " +
                            "CASE WHEN r.status IS NOT NULL AND r.status <> 'LOCKED' " +
                            "AND NOT EXISTS (SELECT 1 FROM `" + ChatConstants.ROUND_TABLE + "` newer WHERE newer.session_id = r.session_id AND newer.round_no > r.round_no) " +
                            "THEN 1 ELSE 0 END AS switchable " +
                            "FROM `" + table + "` l " +
                            "LEFT JOIN `" + ChatConstants.ROUND_TABLE + "` r ON l.round_id = r.id " +
                            "WHERE l.session_id=? AND (l.round_id IS NULL OR r.id IS NULL OR l.id = r.user_message_id OR l.id = r.selected_assistant_message_id) " +
                            "ORDER BY l.created ASC, l.id ASC",
                    (rs, rowNum) -> mapRow(rs),
                    sessionId
            );
            for (ChatMessageRecord record : current) {
                if (record != null && record.getId() != null) {
                    recordMap.putIfAbsent(record.getId(), record);
                }
            }
        }
        return recordMap.values().stream()
                .sorted((a, b) -> {
                    int compare = a.getCreated().compareTo(b.getCreated());
                    if (compare != 0) {
                        return compare;
                    }
                    return a.getId().compareTo(b.getId());
                })
                .collect(Collectors.toList());
    }

    public List<ChatMessageRecord> listRoundVariants(BigInteger sessionId, BigInteger roundId, List<YearMonth> months) {
        List<ChatMessageRecord> records = new ArrayList<>();
        for (YearMonth month : months) {
            String table = tableRouter.resolveLogTable(month);
            records.addAll(jdbcTemplate.query(
                    "SELECT l.*, r.round_no AS joined_round_no, r.variant_count, r.selected_variant_index, " +
                            "0 AS switchable " +
                            "FROM `" + table + "` l " +
                            "INNER JOIN `" + ChatConstants.ROUND_TABLE + "` r ON l.round_id = r.id " +
                            "WHERE l.session_id=? AND l.round_id=? AND l.message_kind=? " +
                            "ORDER BY l.variant_index ASC, l.created ASC, l.id ASC",
                    (rs, rowNum) -> mapRow(rs),
                    sessionId,
                    roundId,
                    ChatConstants.MESSAGE_KIND_ASSISTANT_VARIANT
            ));
        }
        return records;
    }

    /**
     * 精准查询轮次下指定答案版本。
     *
     * @param sessionId 会话 ID
     * @param roundId 轮次 ID
     * @param variantIndex 答案版本序号
     * @param months 查询月份
     * @return 目标答案版本
     */
    public ChatMessageRecord findRoundVariant(BigInteger sessionId, BigInteger roundId, Integer variantIndex, List<YearMonth> months) {
        if (sessionId == null || roundId == null || variantIndex == null || variantIndex <= 0 || months == null || months.isEmpty()) {
            return null;
        }
        for (YearMonth month : months) {
            String table = tableRouter.resolveLogTable(month);
            List<ChatMessageRecord> records = jdbcTemplate.query(
                    "SELECT l.*, r.round_no AS joined_round_no, r.variant_count, r.selected_variant_index, 0 AS switchable " +
                            "FROM `" + table + "` l " +
                            "INNER JOIN `" + ChatConstants.ROUND_TABLE + "` r ON l.round_id = r.id " +
                            "WHERE l.session_id=? AND l.round_id=? AND l.message_kind=? AND l.variant_index=? " +
                            "ORDER BY l.created DESC, l.id DESC LIMIT 1",
                    (rs, rowNum) -> mapRow(rs),
                    sessionId,
                    roundId,
                    ChatConstants.MESSAGE_KIND_ASSISTANT_VARIANT,
                    variantIndex
            );
            if (!records.isEmpty()) {
                return records.get(0);
            }
        }
        return null;
    }

    private int resolveCandidateLimit(long offset, int limit) {
        long normalizedOffset = Math.max(offset, 0);
        long normalizedLimit = Math.max(limit, 1);
        long candidateLimit = normalizedOffset + normalizedLimit;
        return (int) Math.min(candidateLimit, Integer.MAX_VALUE);
    }

    public List<ChatMessageRecord> loadIncremental(String table, Date cursorTime, BigInteger cursorId, int limit) {
        Timestamp timestamp = cursorTime == null ? new Timestamp(0L) : new Timestamp(cursorTime.getTime());
        return jdbcTemplate.query(
                "SELECT * FROM `" + table + "` WHERE (created > ?) OR (created = ? AND id > ?) " +
                        "ORDER BY created ASC, id ASC LIMIT ?",
                (rs, rowNum) -> mapRow(rs),
                timestamp,
                timestamp,
                cursorId == null ? BigInteger.ZERO : cursorId,
                limit
        );
    }

    public List<ChatMessageRecord> loadRepairRows(List<YearMonth> months, Date startTime) {
        if (months.isEmpty()) {
            return Collections.emptyList();
        }
        List<ChatMessageRecord> records = new ArrayList<>();
        for (YearMonth month : months) {
            String table = tableRouter.resolveLogTable(month);
            records.addAll(jdbcTemplate.query(
                    "SELECT * FROM `" + table + "` WHERE created >= ? ORDER BY created ASC, id ASC",
                    (rs, rowNum) -> mapRow(rs),
                    new Timestamp(startTime.getTime())
            ));
        }
        return records;
    }

    private ChatMessageRecord mapRow(ResultSet rs) throws SQLException {
        ChatMessageRecord record = new ChatMessageRecord();
        record.setId(bigInteger(rs, "id"));
        record.setSessionId(bigInteger(rs, "session_id"));
        record.setUserId(bigInteger(rs, "user_id"));
        record.setAssistantId(bigInteger(rs, "assistant_id"));
        record.setRoundId(bigInteger(rs, "round_id"));
        record.setRoundNo(optionalInteger(rs, "round_no"));
        Integer joinedRoundNo = optionalInteger(rs, "joined_round_no");
        if (joinedRoundNo != null) {
            record.setRoundNo(joinedRoundNo);
        }
        record.setSenderId(bigInteger(rs, "sender_id"));
        record.setSenderName(rs.getString("sender_name"));
        record.setSenderRole(rs.getString("sender_role"));
        record.setMessageKind(optionalString(rs, "message_kind"));
        record.setVariantIndex(optionalInteger(rs, "variant_index"));
        record.setVariantCount(optionalInteger(rs, "variant_count"));
        record.setSelectedVariantIndex(optionalInteger(rs, "selected_variant_index"));
        record.setSwitchable(optionalBoolean(rs, "switchable"));
        record.setContentType(rs.getString("content_type"));
        record.setContentText(rs.getString("content_text"));
        record.setContentPayload(jsonSupport.toMap(rs.getString("content_payload")));
        record.setCreated(rs.getTimestamp("created"));
        record.setCreatedBy(bigInteger(rs, "created_by"));
        record.setSyncVersion(rs.getLong("sync_version"));
        return record;
    }

    private BigInteger bigInteger(ResultSet rs, String column) throws SQLException {
        Object value = rs.getObject(column);
        if (value == null) {
            return null;
        }
        return new BigInteger(String.valueOf(value));
    }

    private Integer optionalInteger(ResultSet rs, String column) {
        try {
            Object value = rs.getObject(column);
            if (value == null) {
                return null;
            }
            return Integer.parseInt(String.valueOf(value));
        } catch (SQLException ignored) {
            return null;
        }
    }

    private Boolean optionalBoolean(ResultSet rs, String column) {
        try {
            Object value = rs.getObject(column);
            if (value == null) {
                return null;
            }
            if (value instanceof Boolean booleanValue) {
                return booleanValue;
            }
            return Integer.parseInt(String.valueOf(value)) != 0;
        } catch (SQLException ignored) {
            return null;
        }
    }

    private String optionalString(ResultSet rs, String column) {
        try {
            return rs.getString(column);
        } catch (SQLException ignored) {
            return null;
        }
    }
}
