package mysql

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"

	_ "github.com/go-sql-driver/mysql"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	activitydomain "hyapp/services/activity-service/internal/domain/activity"
)

// Repository 是 activity-service 的 MySQL 存储入口。
type Repository struct {
	db *sql.DB
}

// Open 创建 MySQL 连接池并做启动 ping。
func Open(ctx context.Context, dsn string) (*Repository, error) {
	if strings.TrimSpace(dsn) == "" {
		return nil, xerr.New(xerr.InvalidArgument, "mysql_dsn is required")
	}

	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return nil, err
	}

	if err := db.PingContext(ctx); err != nil {
		_ = db.Close()
		return nil, err
	}

	return &Repository{db: db}, nil
}

// Migrate performs narrow, idempotent local schema fixes for activity-service.
func (r *Repository) Migrate(ctx context.Context) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	if err := r.ensureLuckyDrawPoolID(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyDrawVisibleRegion(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyGiftOutboxColumns(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyGiftOutboxIndexes(ctx); err != nil {
		return err
	}
	if err := r.ensureBroadcastOutboxIndexes(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyDrawRewardSettlementColumns(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyDrawPoolStatsTables(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyGiftRuleVersionTables(ctx); err != nil {
		return err
	}
	if err := r.ensureWeeklyStarTables(ctx); err != nil {
		return err
	}
	if err := r.ensureAgencyOpeningTables(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyUserStateFlowColumns(ctx); err != nil {
		return err
	}
	if err := r.ensureDefaultLuckyGiftRules(ctx); err != nil {
		return err
	}
	if err := r.ensureWheelTables(ctx); err != nil {
		return err
	}
	if err := r.ensureRoomTurnoverRewardTables(ctx); err != nil {
		return err
	}
	if err := r.ensureCumulativeRechargeRewardTables(ctx); err != nil {
		return err
	}
	if err := r.ensureInviteActivityRewardTables(ctx); err != nil {
		return err
	}
	if err := r.ensureCPWeeklyRankTables(ctx); err != nil {
		return err
	}
	if err := r.ensureTaskDefinitionMetadataColumns(ctx); err != nil {
		return err
	}
	return nil
}

func (r *Repository) ensureTaskDefinitionMetadataColumns(ctx context.Context) error {
	// 本地和测试环境会走自动迁移，逐列检查避免旧库重复启动时报 duplicate column；线上仍可用 deploy/mysql/migrations 的显式 SQL。
	additions := []struct {
		column string
		sql    string
	}{
		{"audience_type", `ALTER TABLE task_definitions ADD COLUMN audience_type VARCHAR(32) NOT NULL DEFAULT 'all' COMMENT '适用人群：all/newbie' AFTER description`},
		{"icon_key", `ALTER TABLE task_definitions ADD COLUMN icon_key VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'App 任务图标键' AFTER audience_type`},
		{"icon_url", `ALTER TABLE task_definitions ADD COLUMN icon_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 任务图标 URL' AFTER icon_key`},
		{"action_type", `ALTER TABLE task_definitions ADD COLUMN action_type VARCHAR(64) NOT NULL DEFAULT 'none' COMMENT 'App 跳转类型' AFTER icon_url`},
		{"action_param", `ALTER TABLE task_definitions ADD COLUMN action_param VARCHAR(512) NOT NULL DEFAULT '' COMMENT 'App 跳转参数' AFTER action_type`},
		{"action_payload_json", `ALTER TABLE task_definitions ADD COLUMN action_payload_json JSON NULL COMMENT 'App 跳转扩展 JSON' AFTER action_param`},
		{"dimension_filter_json", `ALTER TABLE task_definitions ADD COLUMN dimension_filter_json JSON NULL COMMENT '任务事件维度过滤 JSON' AFTER action_payload_json`},
	}
	for _, addition := range additions {
		exists, err := r.columnExists(ctx, "task_definitions", addition.column)
		if err != nil {
			return err
		}
		if !exists {
			// 任务定义新增的是展示、跳转和事件过滤元数据，默认值保证旧任务无需人工补字段即可继续查询和领取。
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	eventAdditions := []struct {
		column string
		sql    string
	}{
		{"metric_type", `ALTER TABLE task_event_consumption ADD COLUMN metric_type VARCHAR(64) NOT NULL DEFAULT '' COMMENT '任务指标类型' AFTER status`},
		{"value_delta", `ALTER TABLE task_event_consumption ADD COLUMN value_delta BIGINT NOT NULL DEFAULT 0 COMMENT '本次事件进度增量' AFTER metric_type`},
		{"dimensions_json", `ALTER TABLE task_event_consumption ADD COLUMN dimensions_json JSON NULL COMMENT '事件维度快照 JSON' AFTER value_delta`},
	}
	for _, addition := range eventAdditions {
		exists, err := r.columnExists(ctx, "task_event_consumption", addition.column)
		if err != nil {
			return err
		}
		if !exists {
			// 事件消费表补充 metric/value/dimensions 快照，主要用于排查“事件到了但任务没涨”的过滤原因。
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureLuckyDrawPoolStatsTables(ctx context.Context) error {
	statements := []string{
		`CREATE TABLE IF NOT EXISTS lucky_draw_pool_stats (
		  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
		  pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
		  gift_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '礼物 ID，空值表示奖池汇总',
		  total_draws BIGINT NOT NULL DEFAULT 0 COMMENT '抽奖次数',
		  unique_users BIGINT NOT NULL DEFAULT 0 COMMENT '参与用户数',
		  unique_rooms BIGINT NOT NULL DEFAULT 0 COMMENT '参与房间数',
		  total_spent_coins BIGINT NOT NULL DEFAULT 0 COMMENT '消耗金币',
		  total_reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '返还金币',
		  base_reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '基础 RTP 返奖',
		  room_atmosphere_reward_coins BIGINT NOT NULL DEFAULT 0 COMMENT '房间气氛支出',
		  activity_subsidy_coins BIGINT NOT NULL DEFAULT 0 COMMENT '活动补贴支出',
		  pending_draws BIGINT NOT NULL DEFAULT 0 COMMENT '待发放数量',
		  granted_draws BIGINT NOT NULL DEFAULT 0 COMMENT '已发放数量',
		  failed_draws BIGINT NOT NULL DEFAULT 0 COMMENT '发放失败数量',
		  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
		  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
		  PRIMARY KEY (app_code, pool_id, gift_id)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物池级汇总统计'`,
		`CREATE TABLE IF NOT EXISTS lucky_draw_pool_day_stats (
		  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
		  stat_day DATE NOT NULL COMMENT 'UTC 统计日',
		  visible_region_id BIGINT NOT NULL DEFAULT 0 COMMENT '房间可见区域 ID',
		  pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
		  gift_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '礼物 ID，空值表示奖池汇总',
		  draw_count BIGINT NOT NULL DEFAULT 0 COMMENT '抽奖次数',
		  turnover_coin BIGINT NOT NULL DEFAULT 0 COMMENT '消耗金币',
		  payout_coin BIGINT NOT NULL DEFAULT 0 COMMENT '用户可见返奖金币',
		  base_reward_coin BIGINT NOT NULL DEFAULT 0 COMMENT '基础 RTP 返奖',
		  room_atmosphere_reward_coin BIGINT NOT NULL DEFAULT 0 COMMENT '房间气氛支出',
		  activity_subsidy_coin BIGINT NOT NULL DEFAULT 0 COMMENT '活动补贴支出',
		  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
		  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
		  PRIMARY KEY (app_code, stat_day, visible_region_id, pool_id, gift_id),
		  KEY idx_lucky_draw_pool_day_overview (app_code, stat_day, pool_id)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 Databi 日维度汇总'`,
		`CREATE TABLE IF NOT EXISTS lucky_draw_pool_stat_users (
		  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
		  pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
		  gift_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '礼物 ID，空值表示奖池汇总',
		  user_id BIGINT NOT NULL COMMENT '用户 ID',
		  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
		  PRIMARY KEY (app_code, pool_id, gift_id, user_id)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物汇总用户去重'`,
		`CREATE TABLE IF NOT EXISTS lucky_draw_pool_stat_rooms (
			  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
			  pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
			  gift_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '礼物 ID，空值表示奖池汇总',
			  room_id VARCHAR(96) NOT NULL COMMENT '房间 ID',
			  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
			  PRIMARY KEY (app_code, pool_id, gift_id, room_id)
			) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物汇总房间去重'`,
		`CREATE TABLE IF NOT EXISTS lucky_draw_pool_stat_cursors (
			  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
			  cursor_name VARCHAR(64) NOT NULL COMMENT '游标名称',
			  last_draw_created_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '最后已聚合 draw 创建时间',
			  last_draw_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '同一毫秒内最后已聚合 draw ID',
			  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
			  updated_at_ms BIGINT NOT NULL COMMENT '更新时间，UTC epoch ms',
			  PRIMARY KEY (app_code, cursor_name)
			) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物后台统计快照游标'`,
	}
	for _, statement := range statements {
		if _, err := r.db.ExecContext(ctx, statement); err != nil {
			return err
		}
	}
	return nil
}

func (r *Repository) ensureLuckyUserStateFlowColumns(ctx context.Context) error {
	// 用户阶段改按金币流水折算等价抽数，状态表必须持久保存累计流水和连续未中奖次数，不能再只依赖 paid_draws。
	additions := []struct {
		name string
		sql  string
	}{
		{"cumulative_wager_coins", `ALTER TABLE lucky_user_states ADD COLUMN cumulative_wager_coins BIGINT NOT NULL DEFAULT 0 COMMENT '该用户在当前奖池累计消耗金币' AFTER paid_draws`},
		{"equivalent_draws", `ALTER TABLE lucky_user_states ADD COLUMN equivalent_draws BIGINT NOT NULL DEFAULT 0 COMMENT '累计金币按规则参考价格折算后的等价抽数' AFTER cumulative_wager_coins`},
		{"loss_streak", `ALTER TABLE lucky_user_states ADD COLUMN loss_streak BIGINT NOT NULL DEFAULT 0 COMMENT '连续未获得可见奖励次数，中奖后清零' AFTER equivalent_draws`},
	}
	for _, addition := range additions {
		exists, err := r.columnExists(ctx, "lucky_user_states", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

// Close 释放 MySQL 连接池。
func (r *Repository) Close() error {
	if r == nil || r.db == nil {
		return nil
	}

	return r.db.Close()
}

// Ping 验证 MySQL 当前可用。
func (r *Repository) Ping(ctx context.Context) error {
	if r == nil || r.db == nil {
		return xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}

	return r.db.PingContext(ctx)
}

func (r *Repository) ensureLuckyDrawPoolID(ctx context.Context) error {
	const table = "lucky_draw_records"
	hasPoolID, err := r.columnExists(ctx, table, "pool_id")
	if err != nil {
		return err
	}
	if !hasPoolID {
		if _, err := r.db.ExecContext(ctx, `
			ALTER TABLE lucky_draw_records
			ADD COLUMN pool_id VARCHAR(96) NOT NULL DEFAULT '' COMMENT '幸运礼物奖池 ID' AFTER anchor_id`); err != nil {
			return err
		}
	}
	if _, err := r.db.ExecContext(ctx, `UPDATE lucky_draw_records SET pool_id = gift_id WHERE pool_id = ''`); err != nil {
		return err
	}
	hasPoolIndex, err := r.indexExists(ctx, table, "idx_lucky_draw_pool")
	if err != nil {
		return err
	}
	if !hasPoolIndex {
		if _, err := r.db.ExecContext(ctx, `CREATE INDEX idx_lucky_draw_pool ON lucky_draw_records (app_code, pool_id, created_at_ms)`); err != nil {
			return err
		}
	}
	indexes := []struct {
		name string
		sql  string
	}{
		{"idx_lucky_draw_created", `CREATE INDEX idx_lucky_draw_created ON lucky_draw_records (app_code, created_at_ms, draw_id)`},
		{"idx_lucky_draw_pool_status_summary", `CREATE INDEX idx_lucky_draw_pool_status_summary ON lucky_draw_records (app_code, pool_id, reward_status, created_at_ms, draw_id)`},
		{"idx_lucky_draw_gift_status_summary", `CREATE INDEX idx_lucky_draw_gift_status_summary ON lucky_draw_records (app_code, pool_id, gift_id, reward_status, created_at_ms, draw_id)`},
	}
	for _, addition := range indexes {
		exists, err := r.indexExists(ctx, table, addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureLuckyDrawVisibleRegion(ctx context.Context) error {
	const table = "lucky_draw_records"
	hasVisibleRegion, err := r.columnExists(ctx, table, "visible_region_id")
	if err != nil {
		return err
	}
	if !hasVisibleRegion {
		if _, err := r.db.ExecContext(ctx, `
			ALTER TABLE lucky_draw_records
			ADD COLUMN visible_region_id BIGINT NOT NULL DEFAULT 0 COMMENT '房间可见区域 ID' AFTER anchor_id`); err != nil {
			return err
		}
	}
	hasIndex, err := r.indexExists(ctx, table, "idx_lucky_draw_region_created")
	if err != nil {
		return err
	}
	if !hasIndex {
		if _, err := r.db.ExecContext(ctx, `CREATE INDEX idx_lucky_draw_region_created ON lucky_draw_records (app_code, visible_region_id, created_at_ms, draw_id)`); err != nil {
			return err
		}
	}
	return nil
}

func (r *Repository) ensureLuckyGiftOutboxColumns(ctx context.Context) error {
	additions := []struct {
		name string
		sql  string
	}{
		{"locked_by", `ALTER TABLE activity_outbox ADD COLUMN locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前锁定 worker' AFTER next_retry_at_ms`},
		{"lock_until_ms", `ALTER TABLE activity_outbox ADD COLUMN lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '锁过期时间，UTC epoch ms' AFTER locked_by`},
		{"last_error", `ALTER TABLE activity_outbox ADD COLUMN last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次失败原因' AFTER lock_until_ms`},
	}
	for _, addition := range additions {
		exists, err := r.columnExists(ctx, "activity_outbox", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureLuckyGiftOutboxIndexes(ctx context.Context) error {
	// LuckyGiftDrawn worker 的 pending 和 lock-expired 两条路径分开查，索引列顺序要和各自 WHERE 匹配。
	additions := []struct {
		name string
		sql  string
	}{
		{"idx_activity_outbox_lucky_retry", `CREATE INDEX idx_activity_outbox_lucky_retry ON activity_outbox (app_code, event_type, status, next_retry_at_ms, created_at_ms, outbox_id)`},
		{"idx_activity_outbox_lucky_lock", `CREATE INDEX idx_activity_outbox_lucky_lock ON activity_outbox (app_code, event_type, status, lock_until_ms, created_at_ms, outbox_id)`},
		{"idx_activity_outbox_retention", `CREATE INDEX idx_activity_outbox_retention ON activity_outbox (app_code, status, updated_at_ms, outbox_id)`},
	}
	for _, addition := range additions {
		exists, err := r.indexExists(ctx, "activity_outbox", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureBroadcastOutboxIndexes(ctx context.Context) error {
	additions := []struct {
		name      string
		statement string
	}{
		{"idx_im_broadcast_outbox_retention", `CREATE INDEX idx_im_broadcast_outbox_retention ON im_broadcast_outbox (app_code, status, updated_at_ms, event_id)`},
	}
	for _, addition := range additions {
		exists, err := r.indexExists(ctx, "im_broadcast_outbox", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.statement); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureLuckyDrawRewardSettlementColumns(ctx context.Context) error {
	additions := []struct {
		name string
		sql  string
	}{
		{"reward_transaction_id", `ALTER TABLE lucky_draw_records ADD COLUMN reward_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '返奖钱包交易 ID' AFTER reward_status`},
		{"reward_failure_reason", `ALTER TABLE lucky_draw_records ADD COLUMN reward_failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '返奖失败原因' AFTER reward_transaction_id`},
	}
	for _, addition := range additions {
		exists, err := r.columnExists(ctx, "lucky_draw_records", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureLuckyGiftRuleVersionTables(ctx context.Context) error {
	statements := []string{
		`CREATE TABLE IF NOT EXISTS lucky_gift_rule_versions (
		  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
		  pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
		  rule_version BIGINT NOT NULL COMMENT '不可变规则版本',
		  enabled TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否启用',
		  target_rtp_ppm BIGINT NOT NULL COMMENT '基础返奖目标，ppm',
		  pool_rate_ppm BIGINT NOT NULL COMMENT '每笔扣费进入基础奖池比例，ppm',
		  settlement_window_wager BIGINT NOT NULL COMMENT 'RTP 结算窗口流水，单位金币',
		  control_band_ppm BIGINT NOT NULL COMMENT '正常波动带，ppm',
		  gift_price_reference BIGINT NOT NULL COMMENT '配置参考礼物价格',
		  novice_max_equivalent_draws BIGINT NOT NULL DEFAULT 2000 COMMENT '新手阶段截止等价抽数，按累计金币/参考价格折算',
		  normal_max_equivalent_draws BIGINT NOT NULL DEFAULT 20000 COMMENT '正常阶段截止等价抽数，超过后进入高阶阶段',
		  max_single_payout BIGINT NOT NULL DEFAULT 50000 COMMENT '单次基础返奖上限，按参考价格配置',
		  user_hourly_payout_cap BIGINT NOT NULL DEFAULT 34200 COMMENT '单用户每小时基础返奖上限，按参考价格配置',
		  user_daily_payout_cap BIGINT NOT NULL DEFAULT 615600 COMMENT '单用户每日基础返奖上限，按参考价格配置',
		  device_daily_payout_cap BIGINT NOT NULL DEFAULT 1026000 COMMENT '单设备每日基础返奖上限，按参考价格配置',
		  room_hourly_payout_cap BIGINT NOT NULL DEFAULT 684000 COMMENT '单房间每小时基础返奖上限，按参考价格配置',
		  anchor_daily_payout_cap BIGINT NOT NULL DEFAULT 12312000 COMMENT '单主播每日基础返奖上限，按参考价格配置',
		  effective_from_ms BIGINT NOT NULL COMMENT '生效时间，UTC epoch ms',
		  created_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '发布人',
		  created_at_ms BIGINT NOT NULL COMMENT '创建时间，UTC epoch ms',
		  PRIMARY KEY (app_code, pool_id, rule_version),
		  KEY idx_lucky_gift_rule_versions_latest (app_code, pool_id, rule_version),
		  KEY idx_lucky_gift_rule_versions_enabled (app_code, enabled, created_at_ms)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 v2 规则版本表'`,
		`CREATE TABLE IF NOT EXISTS lucky_gift_stage_tiers (
		  app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码',
		  pool_id VARCHAR(96) NOT NULL COMMENT '幸运礼物奖池 ID',
		  rule_version BIGINT NOT NULL COMMENT '规则版本',
		  stage VARCHAR(32) NOT NULL COMMENT 'novice/normal/advanced',
		  tier_id VARCHAR(96) NOT NULL COMMENT '奖档 ID',
		  multiplier_ppm BIGINT NOT NULL COMMENT '倍率，1x = 1000000',
		  base_weight_ppm BIGINT NOT NULL COMMENT '正常模式基础概率，ppm',
		  reward_source VARCHAR(32) NOT NULL COMMENT 'base_rtp/activity_subsidy/presentation_only',
		  high_water_only TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否高水位才开放',
		  broadcast_level VARCHAR(32) NOT NULL DEFAULT 'none' COMMENT 'none/room/region/global',
		  enabled TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
		  PRIMARY KEY (app_code, pool_id, rule_version, stage, tier_id),
		  KEY idx_lucky_gift_stage_tiers_stage (app_code, pool_id, rule_version, stage)
		) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='幸运礼物 v2 阶段奖档表'`,
	}
	for _, statement := range statements {
		if _, err := r.db.ExecContext(ctx, statement); err != nil {
			return err
		}
	}
	if err := r.ensureLuckyGiftRuleVersionStageThresholdColumns(ctx); err != nil {
		return err
	}
	if err := r.ensureLuckyGiftRuleVersionRiskCapColumns(ctx); err != nil {
		return err
	}
	return nil
}

func (r *Repository) ensureLuckyGiftRuleVersionStageThresholdColumns(ctx context.Context) error {
	// 阶段阈值从硬编码抽数升级为“累计金币流水折算等价抽数”；旧开发库重启后必须自动补列，避免线上逻辑读到零阈值。
	additions := []struct {
		name string
		sql  string
	}{
		{"novice_max_equivalent_draws", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN novice_max_equivalent_draws BIGINT NOT NULL DEFAULT 2000 COMMENT '新手阶段截止等价抽数，按累计金币/参考价格折算' AFTER gift_price_reference`},
		{"normal_max_equivalent_draws", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN normal_max_equivalent_draws BIGINT NOT NULL DEFAULT 20000 COMMENT '正常阶段截止等价抽数，超过后进入高阶阶段' AFTER novice_max_equivalent_draws`},
	}
	for _, addition := range additions {
		exists, err := r.columnExists(ctx, "lucky_gift_rule_versions", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureLuckyGiftRuleVersionRiskCapColumns(ctx context.Context) error {
	// 开发库可能已经存在旧 v2 表；这里做幂等补列，保证服务重启后后台立即可以读写风控上限。
	additions := []struct {
		name string
		sql  string
	}{
		{"max_single_payout", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN max_single_payout BIGINT NOT NULL DEFAULT 50000 COMMENT '单次基础返奖上限，按参考价格配置' AFTER gift_price_reference`},
		{"user_hourly_payout_cap", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN user_hourly_payout_cap BIGINT NOT NULL DEFAULT 34200 COMMENT '单用户每小时基础返奖上限，按参考价格配置' AFTER max_single_payout`},
		{"user_daily_payout_cap", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN user_daily_payout_cap BIGINT NOT NULL DEFAULT 615600 COMMENT '单用户每日基础返奖上限，按参考价格配置' AFTER user_hourly_payout_cap`},
		{"device_daily_payout_cap", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN device_daily_payout_cap BIGINT NOT NULL DEFAULT 1026000 COMMENT '单设备每日基础返奖上限，按参考价格配置' AFTER user_daily_payout_cap`},
		{"room_hourly_payout_cap", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN room_hourly_payout_cap BIGINT NOT NULL DEFAULT 684000 COMMENT '单房间每小时基础返奖上限，按参考价格配置' AFTER device_daily_payout_cap`},
		{"anchor_daily_payout_cap", `ALTER TABLE lucky_gift_rule_versions ADD COLUMN anchor_daily_payout_cap BIGINT NOT NULL DEFAULT 12312000 COMMENT '单主播每日基础返奖上限，按参考价格配置' AFTER room_hourly_payout_cap`},
	}
	for _, addition := range additions {
		exists, err := r.columnExists(ctx, "lucky_gift_rule_versions", addition.name)
		if err != nil {
			return err
		}
		if !exists {
			if _, err := r.db.ExecContext(ctx, addition.sql); err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repository) ensureDefaultLuckyGiftRules(ctx context.Context) error {
	// 本地和 Docker 开发库通常保留 MySQL volume；这里补齐 v2 lucky/super_lucky 契约，但不覆盖后台已发布版本。
	const seedNowMS int64 = 1779259000000
	if _, err := r.db.ExecContext(ctx, `
		INSERT IGNORE INTO lucky_gift_rule_versions (
		  app_code, pool_id, rule_version, enabled, target_rtp_ppm, pool_rate_ppm,
		  settlement_window_wager, control_band_ppm, gift_price_reference,
		  novice_max_equivalent_draws, normal_max_equivalent_draws,
		  max_single_payout, user_hourly_payout_cap, user_daily_payout_cap, device_daily_payout_cap,
		  room_hourly_payout_cap, anchor_daily_payout_cap, effective_from_ms, created_by_admin_id, created_at_ms
		) VALUES
		  ('lalu', 'lucky', 1, true, 950000, 960000, 1000000, 10000, 100, 2000, 20000, 50000, 34200, 615600, 1026000, 684000, 12312000, ?, 0, ?),
		  ('lalu', 'super_lucky', 1, true, 950000, 960000, 1000000, 10000, 100, 2000, 20000, 50000, 34200, 615600, 1026000, 684000, 12312000, ?, 0, ?)`,
		seedNowMS, seedNowMS, seedNowMS, seedNowMS,
	); err != nil {
		return err
	}
	_, err := r.db.ExecContext(ctx, `
		INSERT IGNORE INTO lucky_gift_stage_tiers (
		  app_code, pool_id, rule_version, stage, tier_id, multiplier_ppm, base_weight_ppm,
		  reward_source, high_water_only, broadcast_level, enabled
		) VALUES
		  ('lalu', 'lucky', 1, 'novice', 'novice_none', 0, 100000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'novice', 'novice_0_5x', 500000, 200000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'novice', 'novice_1x', 1000000, 550000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'novice', 'novice_2x', 2000000, 150000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'normal', 'normal_none', 0, 100000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'normal', 'normal_0_5x', 500000, 200000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'normal', 'normal_1x', 1000000, 550000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'normal', 'normal_2x', 2000000, 150000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'advanced', 'advanced_none', 0, 270000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'advanced', 'advanced_1x', 1000000, 600000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'advanced', 'advanced_2x', 2000000, 100000, 'base_rtp', false, 'none', true),
		  ('lalu', 'lucky', 1, 'advanced', 'advanced_5x', 5000000, 30000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'novice', 'novice_none', 0, 100000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'novice', 'novice_0_5x', 500000, 200000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'novice', 'novice_1x', 1000000, 550000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'novice', 'novice_2x', 2000000, 150000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'normal', 'normal_none', 0, 100000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'normal', 'normal_0_5x', 500000, 200000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'normal', 'normal_1x', 1000000, 550000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'normal', 'normal_2x', 2000000, 150000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'advanced', 'advanced_none', 0, 270000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'advanced', 'advanced_1x', 1000000, 600000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'advanced', 'advanced_2x', 2000000, 100000, 'base_rtp', false, 'none', true),
		  ('lalu', 'super_lucky', 1, 'advanced', 'advanced_5x', 5000000, 30000, 'base_rtp', false, 'none', true)`)
	return err
}

func (r *Repository) columnExists(ctx context.Context, table string, column string) (bool, error) {
	database, err := r.currentDatabase(ctx)
	if err != nil {
		return false, err
	}
	var count int
	if err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM information_schema.COLUMNS
		WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?`,
		database, table, column,
	).Scan(&count); err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *Repository) indexExists(ctx context.Context, table string, index string) (bool, error) {
	database, err := r.currentDatabase(ctx)
	if err != nil {
		return false, err
	}
	var count int
	if err := r.db.QueryRowContext(ctx, `
		SELECT COUNT(*)
		FROM information_schema.STATISTICS
		WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ?`,
		database, table, index,
	).Scan(&count); err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *Repository) currentDatabase(ctx context.Context) (string, error) {
	var database sql.NullString
	if err := r.db.QueryRowContext(ctx, `SELECT DATABASE()`).Scan(&database); err != nil {
		return "", err
	}
	if !database.Valid || strings.TrimSpace(database.String) == "" {
		return "", fmt.Errorf("mysql database is not selected")
	}
	return database.String, nil
}

// GetActivity 从 MySQL 活动配置表读取同步查询投影。
func (r *Repository) GetActivity(ctx context.Context, activityID string) (activitydomain.Activity, error) {
	if r == nil || r.db == nil {
		return activitydomain.Activity{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}

	row := r.db.QueryRowContext(ctx,
		`SELECT app_code, activity_id, status, updated_at_ms
		 FROM activities
		 WHERE app_code = ? AND activity_id = ?`,
		appcode.FromContext(ctx),
		activityID,
	)

	var activity activitydomain.Activity
	var status string
	if err := row.Scan(&activity.AppCode, &activity.ActivityID, &status, &activity.UpdatedAtMs); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			// 未配置活动时返回 NotFound，上层可决定是否降级为 inactive。
			return activitydomain.Activity{}, xerr.New(xerr.NotFound, "activity not found")
		}

		return activitydomain.Activity{}, err
	}
	activity.Status = activitydomain.Status(status)

	return activity, nil
}
