package tech.easyflow.chatlog.repository.mysql;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import tech.easyflow.chatlog.domain.command.ChatSessionSummaryCommand;
import tech.easyflow.chatlog.domain.command.ChatSessionUpsertCommand;
import tech.easyflow.chatlog.domain.dto.ChatSessionSummary;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionDeletePayload;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionRenamePayload;
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
import tech.easyflow.chatlog.support.ChatTableRouter;

import java.math.BigInteger;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.*;

@Repository
public class MySqlChatSessionRepository {

    private final JdbcTemplate jdbcTemplate;
    private final ChatTableRouter tableRouter;

    public MySqlChatSessionRepository(JdbcTemplate jdbcTemplate, ChatTableRouter tableRouter) {
        this.jdbcTemplate = jdbcTemplate;
        this.tableRouter = tableRouter;
    }

    public void createOrTouchBatch(List<ChatSessionUpsertCommand> commands) {
        if (commands == null || commands.isEmpty()) {
            return;
        }
        String table = tableRouter.resolveSessionTable();
        String sql = "INSERT INTO `" + table + "` " +
                "(id, tenant_id, dept_id, user_id, user_account, assistant_id, assistant_code, assistant_name, title, ext_json, last_message_preview, message_count, access_at, created, created_by, modified, modified_by, is_deleted) " +
                "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, '', 0, ?, ?, ?, ?, ?, 0) " +
                "ON DUPLICATE KEY UPDATE user_account=COALESCE(NULLIF(VALUES(user_account), ''), user_account), " +
                "assistant_id=VALUES(assistant_id), assistant_code=COALESCE(NULLIF(VALUES(assistant_code), ''), assistant_code), " +
                "assistant_name=COALESCE(NULLIF(VALUES(assistant_name), ''), assistant_name), " +
                "ext_json=COALESCE(VALUES(ext_json), ext_json), " +
                "title=COALESCE(NULLIF(VALUES(title), ''), title), " +
                "access_at=VALUES(access_at), modified=VALUES(modified), modified_by=VALUES(modified_by), is_deleted=0";
        jdbcTemplate.batchUpdate(sql, commands, commands.size(), (ps, command) -> {
            Timestamp operateAt = timestamp(command.getOperateAt());
            ps.setObject(1, command.getSessionId());
            ps.setObject(2, command.getTenantId());
            ps.setObject(3, command.getDeptId());
            ps.setObject(4, command.getUserId());
            ps.setString(5, safeString(command.getUserAccount()));
            ps.setObject(6, command.getAssistantId());
            ps.setString(7, safeString(command.getAssistantCode()));
            ps.setString(8, safeString(command.getAssistantName()));
            ps.setString(9, safeString(command.getTitle()));
            setNullableJson(ps, 10, command.getExtJson());
            ps.setTimestamp(11, operateAt);
            ps.setTimestamp(12, operateAt);
            ps.setObject(13, command.getOperatorId());
            ps.setTimestamp(14, operateAt);
            ps.setObject(15, command.getOperatorId());
        });
    }

    public void updateSummaries(List<ChatSessionSummaryCommand> commands) {
        if (commands == null || commands.isEmpty()) {
            return;
        }
        String table = tableRouter.resolveSessionTable();
        String sql = "UPDATE `" + table + "` SET " +
                "last_sender_id=CASE WHEN ?=1 OR last_message_at IS NULL OR last_message_at <= ? THEN ? ELSE last_sender_id END, " +
                "last_sender_name=CASE WHEN ?=1 OR last_message_at IS NULL OR last_message_at <= ? THEN ? ELSE last_sender_name END, " +
                "last_message_preview=CASE WHEN ?=1 OR last_message_at IS NULL OR last_message_at <= ? THEN ? ELSE last_message_preview END, " +
                "last_message_at=CASE WHEN ?=1 OR last_message_at IS NULL OR last_message_at <= ? THEN ? ELSE last_message_at END, " +
                "access_at=CASE WHEN ?=1 OR last_message_at IS NULL OR last_message_at <= ? THEN ? ELSE access_at END, " +
                "message_count=COALESCE(message_count, 0) + ?, " +
                "modified=?, modified_by=? " +
                "WHERE id=?";
        jdbcTemplate.batchUpdate(sql, commands, commands.size(), (ps, command) -> {
            Timestamp lastMessageAt = timestamp(command.getLastMessageAt());
            Timestamp accessAt = timestamp(command.getAccessAt());
            Timestamp modifiedAt = timestamp(command.getModifiedAt());
            int forceOverwrite = command.isForceOverwrite() ? 1 : 0;
            ps.setInt(1, forceOverwrite);
            ps.setTimestamp(2, lastMessageAt);
            ps.setObject(3, command.getLastSenderId());
            ps.setInt(4, forceOverwrite);
            ps.setTimestamp(5, lastMessageAt);
            ps.setString(6, command.getLastSenderName());
            ps.setInt(7, forceOverwrite);
            ps.setTimestamp(8, lastMessageAt);
            ps.setString(9, safeString(command.getLastMessagePreview()));
            ps.setInt(10, forceOverwrite);
            ps.setTimestamp(11, lastMessageAt);
            ps.setTimestamp(12, lastMessageAt);
            ps.setInt(13, forceOverwrite);
            ps.setTimestamp(14, lastMessageAt);
            ps.setTimestamp(15, accessAt);
            ps.setInt(16, Math.max(command.getMessageIncrement(), 0));
            ps.setTimestamp(17, modifiedAt);
            ps.setObject(18, command.getOperatorId());
            ps.setObject(19, command.getSessionId());
        });
    }

    public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, ChatPageQuery query) {
        return listSessions(userId, assistantId, null, query);
    }

    public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
        String table = tableRouter.resolveSessionTable();
        List<Object> params = new ArrayList<>();
        StringBuilder sql = new StringBuilder("SELECT * FROM `").append(table)
                .append("` WHERE user_id=? AND is_deleted=0 AND last_message_at IS NOT NULL");
        params.add(userId);
        if (assistantId != null) {
            sql.append(" AND assistant_id=?");
            params.add(assistantId);
        }
        if (assistantCode != null && !assistantCode.isBlank()) {
            sql.append(" AND assistant_code=?");
            params.add(assistantCode);
        }
        sql.append(" ORDER BY last_message_at DESC, id DESC LIMIT ? OFFSET ?");
        params.add(query.getPageSize());
        params.add(query.getOffset());
        return jdbcTemplate.query(sql.toString(), sessionRowMapper(), params.toArray());
    }

    public long countSessions(BigInteger userId, BigInteger assistantId) {
        return countSessions(userId, assistantId, null);
    }

    public long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
        String table = tableRouter.resolveSessionTable();
        List<Object> params = new ArrayList<>();
        StringBuilder sql = new StringBuilder("SELECT COUNT(1) FROM `").append(table)
                .append("` WHERE user_id=? AND is_deleted=0 AND last_message_at IS NOT NULL");
        params.add(userId);
        if (assistantId != null) {
            sql.append(" AND assistant_id=?");
            params.add(assistantId);
        }
        if (assistantCode != null && !assistantCode.isBlank()) {
            sql.append(" AND assistant_code=?");
            params.add(assistantCode);
        }
        Long count = jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray());
        return count == null ? 0L : count;
    }

    public ChatSessionSummary findBySessionIdAndUserId(BigInteger sessionId, BigInteger userId) {
        String table = tableRouter.resolveSessionTable();
        List<ChatSessionSummary> list = jdbcTemplate.query(
                "SELECT * FROM `" + table + "` WHERE id=? AND user_id=? AND is_deleted=0 LIMIT 1",
                sessionRowMapper(),
                sessionId,
                userId
        );
        return list.isEmpty() ? null : list.get(0);
    }

    public ChatSessionSummary findBySessionId(BigInteger sessionId) {
        String table = tableRouter.resolveSessionTable();
        List<ChatSessionSummary> list = jdbcTemplate.query(
                "SELECT * FROM `" + table + "` WHERE id=? AND is_deleted=0 LIMIT 1",
                sessionRowMapper(),
                sessionId
        );
        if (!list.isEmpty()) {
            return list.get(0);
        }
        return null;
    }

    public List<ChatSessionSummary> findBySessionIds(List<BigInteger> sessionIds) {
        if (sessionIds == null || sessionIds.isEmpty()) {
            return List.of();
        }
        String table = tableRouter.resolveSessionTable();
        StringJoiner placeholders = new StringJoiner(", ");
        for (int i = 0; i < sessionIds.size(); i++) {
            placeholders.add("?");
        }
        List<ChatSessionSummary> sessions = jdbcTemplate.query(
                "SELECT * FROM `" + table + "` WHERE id IN (" + placeholders + ") AND is_deleted=0",
                sessionRowMapper(),
                sessionIds.toArray()
        );
        Map<BigInteger, ChatSessionSummary> sessionMap = new LinkedHashMap<>();
        for (ChatSessionSummary session : sessions) {
            sessionMap.put(session.getId(), session);
        }
        List<ChatSessionSummary> ordered = new ArrayList<>(sessionIds.size());
        for (BigInteger sessionId : sessionIds) {
            ChatSessionSummary session = sessionMap.get(sessionId);
            if (session != null) {
                ordered.add(session);
            }
        }
        return ordered;
    }

    public void renameSessions(List<ChatSessionRenamePayload> payloads) {
        if (payloads == null || payloads.isEmpty()) {
            return;
        }
        String table = tableRouter.resolveSessionTable();
        String sql = "UPDATE `" + table + "` SET title=?, modified=?, modified_by=? WHERE id=? AND user_id=? AND is_deleted=0";
        jdbcTemplate.batchUpdate(sql, payloads, payloads.size(), (ps, payload) -> {
            ps.setString(1, payload.getTitle());
            ps.setTimestamp(2, timestamp(payload.getOperateAt()));
            ps.setObject(3, payload.getOperatorId());
            ps.setObject(4, payload.getSessionId());
            ps.setObject(5, payload.getUserId());
        });
    }

    public void deleteSessions(List<ChatSessionDeletePayload> payloads) {
        if (payloads == null || payloads.isEmpty()) {
            return;
        }
        String table = tableRouter.resolveSessionTable();
        String sql = "UPDATE `" + table + "` SET is_deleted=1, modified=?, modified_by=? WHERE id=? AND user_id=? AND is_deleted=0";
        jdbcTemplate.batchUpdate(sql, payloads, payloads.size(), (ps, payload) -> {
            ps.setTimestamp(1, timestamp(payload.getOperateAt()));
            ps.setObject(2, payload.getOperatorId());
            ps.setObject(3, payload.getSessionId());
            ps.setObject(4, payload.getUserId());
        });
    }

    public List<ChatSessionSummary> loadModifiedAfter(Date cursorTime, BigInteger cursorId, int limit) {
        String table = tableRouter.resolveSessionTable();
        Timestamp timestamp = cursorTime == null ? new Timestamp(0L) : new Timestamp(cursorTime.getTime());
        return jdbcTemplate.query(
                "SELECT * FROM `" + table + "` WHERE (modified > ?) OR (modified = ? AND id > ?) " +
                        "ORDER BY modified ASC, id ASC LIMIT ?",
                sessionRowMapper(),
                timestamp,
                timestamp,
                cursorId == null ? BigInteger.ZERO : cursorId,
                limit
        );
    }

    public int deleteExpiredSessions(Date beforeTime, int limit) {
        String table = tableRouter.resolveSessionTable();
        return jdbcTemplate.update(
                "DELETE FROM `" + table + "` WHERE access_at < ? LIMIT " + limit,
                new Timestamp(beforeTime.getTime())
        );
    }

    private RowMapper<ChatSessionSummary> sessionRowMapper() {
        return new RowMapper<>() {
            @Override
            public ChatSessionSummary mapRow(ResultSet rs, int rowNum) throws SQLException {
                ChatSessionSummary summary = new ChatSessionSummary();
                summary.setId(bigInteger(rs, "id"));
                summary.setTenantId(bigInteger(rs, "tenant_id"));
                summary.setDeptId(bigInteger(rs, "dept_id"));
                summary.setUserId(bigInteger(rs, "user_id"));
                summary.setUserAccount(rs.getString("user_account"));
                summary.setAssistantId(bigInteger(rs, "assistant_id"));
                summary.setAssistantCode(rs.getString("assistant_code"));
                summary.setAssistantName(rs.getString("assistant_name"));
                summary.setTitle(rs.getString("title"));
                summary.setExtJson(rs.getString("ext_json"));
                summary.setLastMessagePreview(rs.getString("last_message_preview"));
                summary.setLastSenderId(bigInteger(rs, "last_sender_id"));
                summary.setLastSenderName(rs.getString("last_sender_name"));
                summary.setMessageCount(rs.getInt("message_count"));
                summary.setAccessAt(rs.getTimestamp("access_at"));
                summary.setLastMessageAt(rs.getTimestamp("last_message_at"));
                summary.setCreated(rs.getTimestamp("created"));
                summary.setCreatedBy(bigInteger(rs, "created_by"));
                summary.setModified(rs.getTimestamp("modified"));
                summary.setModifiedBy(bigInteger(rs, "modified_by"));
                summary.setIsDeleted(rs.getInt("is_deleted"));
                return summary;
            }
        };
    }

    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 Timestamp timestamp(Date value) {
        return new Timestamp((value == null ? new Date() : value).getTime());
    }

    private String safeString(String value) {
        return value == null ? "" : value;
    }

    private void setNullableJson(java.sql.PreparedStatement ps, int parameterIndex, String value) throws SQLException {
        if (value == null || value.isBlank()) {
            ps.setNull(parameterIndex, Types.VARCHAR);
            return;
        }
        ps.setString(parameterIndex, value);
    }
}
