package mysql

import (
	"context"
	"strings"

	"hyapp/pkg/appcode"
	roomservice "hyapp/services/room-service/internal/room/service"
)

// ApplyRoomRedPacketProjectionEvent 在 wallet 事件消费事务内维护 packet 级状态，再聚合同房间是否仍有有效红包。
// 高频列表只读 room_list_entries 同一行；这里的 MAX 查询只在红包状态变化时执行，并命中
// idx_room_red_packet_active(app_code, room_id, status, expires_at_ms)。
func (r *Repository) ApplyRoomRedPacketProjectionEvent(ctx context.Context, event roomservice.RoomRedPacketProjectionEvent) (roomservice.RoomListEntry, bool, error) {
	app := normalizedRecordAppCode(ctx, "")
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return roomservice.RoomListEntry{}, false, err
	}
	defer func() { _ = tx.Rollback() }()

	// consumption 主键先占位；后续任一步失败会随事务回滚，at-least-once 重投仍可完整重做。
	result, err := tx.ExecContext(ctx, `
		INSERT IGNORE INTO room_wallet_event_consumption (app_code, event_id, event_type, consumed_at_ms)
		VALUES (?, ?, ?, ?)`, app, event.EventID, event.EventType, event.ProjectedAtMS)
	if err != nil {
		return roomservice.RoomListEntry{}, false, err
	}
	inserted, err := result.RowsAffected()
	if err != nil {
		return roomservice.RoomListEntry{}, false, err
	}
	if inserted > 0 {
		// 乱序保护按事实时间、终态等级、剩余份数依次收敛；同毫秒的晚到 created 不能覆盖 claimed/refunded。
		const newer = `(VALUES(event_occurred_at_ms) > event_occurred_at_ms OR
			(VALUES(event_occurred_at_ms) = event_occurred_at_ms AND VALUES(event_rank) > event_rank) OR
			(VALUES(event_occurred_at_ms) = event_occurred_at_ms AND VALUES(event_rank) = event_rank AND VALUES(remaining_count) <= remaining_count))`
		_, err = tx.ExecContext(ctx, `
			INSERT INTO room_red_packet_projections (
				app_code, packet_id, room_id, status, remaining_amount, remaining_count,
				expires_at_ms, event_rank, event_occurred_at_ms, updated_at_ms
			) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
			ON DUPLICATE KEY UPDATE
				room_id = IF(`+newer+`, VALUES(room_id), room_id),
				status = IF(`+newer+`, VALUES(status), status),
				remaining_amount = IF(`+newer+`, VALUES(remaining_amount), remaining_amount),
				remaining_count = IF(`+newer+`, VALUES(remaining_count), remaining_count),
				expires_at_ms = IF(`+newer+` AND VALUES(expires_at_ms) > 0, VALUES(expires_at_ms), expires_at_ms),
				event_rank = IF(`+newer+`, VALUES(event_rank), event_rank),
				event_occurred_at_ms = IF(`+newer+`, VALUES(event_occurred_at_ms), event_occurred_at_ms),
				updated_at_ms = IF(`+newer+`, VALUES(updated_at_ms), updated_at_ms)`,
			app, event.PacketID, event.RoomID, event.Status, event.RemainingAmount, event.RemainingCount,
			event.ExpiresAtMS, event.EventRank, event.OccurredAtMS, event.ProjectedAtMS,
		)
		if err != nil {
			return roomservice.RoomListEntry{}, false, err
		}

		var activeExpiresAtMS int64
		if err := tx.QueryRowContext(ctx, `
			SELECT COALESCE(MAX(expires_at_ms), 0)
			FROM room_red_packet_projections
			WHERE app_code = ? AND room_id = ?
			  AND status IN ('waiting_open', 'active')
			  AND remaining_count > 0 AND remaining_amount > 0
			  AND expires_at_ms > ?`, app, event.RoomID, event.ProjectedAtMS).Scan(&activeExpiresAtMS); err != nil {
			return roomservice.RoomListEntry{}, false, err
		}
		if _, err := tx.ExecContext(ctx, `
			UPDATE room_list_entries
			SET has_active_red_packet = ?, active_red_packet_expires_at_ms = ?
			WHERE app_code = ? AND room_id = ?`, activeExpiresAtMS > 0, activeExpiresAtMS, app, event.RoomID); err != nil {
			return roomservice.RoomListEntry{}, false, err
		}
	}
	if err := tx.Commit(); err != nil {
		return roomservice.RoomListEntry{}, false, err
	}

	// 幂等重投也重新读取卡片：若首次事务已提交但 Redis 刷新失败，重投仍能把缓存收敛到 MySQL。
	return r.roomListEntryByID(ctx, app, event.RoomID)
}

func (r *Repository) roomListEntryByID(ctx context.Context, app string, roomID string) (roomservice.RoomListEntry, bool, error) {
	rows, err := r.db.QueryContext(ctx, roomListSelectColumns+`
		WHERE app_code = ? AND room_id = ?
		LIMIT 1`, appcode.Normalize(app), strings.TrimSpace(roomID))
	if err != nil {
		return roomservice.RoomListEntry{}, false, err
	}
	defer rows.Close()
	if !rows.Next() {
		return roomservice.RoomListEntry{}, false, rows.Err()
	}
	entry, err := scanRoomListEntry(rows)
	if err != nil {
		return roomservice.RoomListEntry{}, false, err
	}
	return entry, true, rows.Err()
}
