package mysql

import (
	"context"
	"database/sql"
	"errors"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	"sort"
	"strings"
	"time"
)

// GetRedPacketConfig 返回红包配置；未配置时返回关闭状态和空档位，调用方不需要把 NotFound 当错误处理。
func (r *Repository) GetRedPacketConfig(ctx context.Context, app string) (ledger.RedPacketConfig, error) {
	if r == nil || r.db == nil {
		return ledger.RedPacketConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, app)
	config, err := r.getRedPacketConfig(ctx, r.db)
	if err != nil {
		return ledger.RedPacketConfig{}, err
	}
	return config, nil
}

// UpdateRedPacketConfig 原子替换红包配置和档位；发送路径只读取 active 档位。
func (r *Repository) UpdateRedPacketConfig(ctx context.Context, config ledger.RedPacketConfig) (ledger.RedPacketConfig, error) {
	if r == nil || r.db == nil {
		return ledger.RedPacketConfig{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	ctx = contextWithCommandApp(ctx, config.AppCode)
	config.AppCode = appcode.FromContext(ctx)
	nowMS := time.Now().UTC().UnixMilli()

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return ledger.RedPacketConfig{}, err
	}
	defer func() { _ = tx.Rollback() }()

	if _, err := tx.ExecContext(ctx, `
		INSERT INTO red_packet_configs (
			app_code, enabled, delayed_open_seconds, expire_seconds, daily_send_limit, rule_url,
			updated_by_admin_id, created_at_ms, updated_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE
			enabled = VALUES(enabled),
			delayed_open_seconds = VALUES(delayed_open_seconds),
			expire_seconds = VALUES(expire_seconds),
			daily_send_limit = VALUES(daily_send_limit),
			rule_url = VALUES(rule_url),
			updated_by_admin_id = VALUES(updated_by_admin_id),
			updated_at_ms = VALUES(updated_at_ms)`,
		config.AppCode,
		config.Enabled,
		config.DelayedOpenSeconds,
		ledger.RedPacketExpireSeconds,
		config.DailySendLimit,
		strings.TrimSpace(config.RuleURL),
		config.UpdatedByAdminID,
		nowMS,
		nowMS,
	); err != nil {
		return ledger.RedPacketConfig{}, err
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM red_packet_count_tiers WHERE app_code = ?`, config.AppCode); err != nil {
		return ledger.RedPacketConfig{}, err
	}
	for index, count := range config.CountTiers {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO red_packet_count_tiers (
				app_code, packet_count, status, sort_order, created_at_ms, updated_at_ms
			) VALUES (?, ?, 'active', ?, ?, ?)`,
			config.AppCode, count, index, nowMS, nowMS,
		); err != nil {
			return ledger.RedPacketConfig{}, err
		}
	}
	if _, err := tx.ExecContext(ctx, `DELETE FROM red_packet_amount_tiers WHERE app_code = ?`, config.AppCode); err != nil {
		return ledger.RedPacketConfig{}, err
	}
	for index, amount := range config.AmountTiers {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO red_packet_amount_tiers (
				app_code, total_amount, status, sort_order, created_at_ms, updated_at_ms
			) VALUES (?, ?, 'active', ?, ?, ?)`,
			config.AppCode, amount, index, nowMS, nowMS,
		); err != nil {
			return ledger.RedPacketConfig{}, err
		}
	}
	if err := tx.Commit(); err != nil {
		return ledger.RedPacketConfig{}, err
	}
	return r.GetRedPacketConfig(ctx, config.AppCode)
}

func (r *Repository) getRedPacketConfig(ctx context.Context, querier redPacketQuerier) (ledger.RedPacketConfig, error) {
	row := querier.QueryRowContext(ctx, `
		SELECT app_code, enabled, delayed_open_seconds, expire_seconds, daily_send_limit,
		       updated_by_admin_id, COALESCE(rule_url, ''), created_at_ms, updated_at_ms
		FROM red_packet_configs
		WHERE app_code = ?`,
		appcode.FromContext(ctx),
	)
	config := ledger.RedPacketConfig{AppCode: appcode.FromContext(ctx), ExpireSeconds: ledger.RedPacketExpireSeconds}
	if err := row.Scan(
		&config.AppCode,
		&config.Enabled,
		&config.DelayedOpenSeconds,
		&config.ExpireSeconds,
		&config.DailySendLimit,
		&config.UpdatedByAdminID,
		&config.RuleURL,
		&config.CreatedAtMS,
		&config.UpdatedAtMS,
	); err != nil {
		if !errors.Is(err, sql.ErrNoRows) {
			return ledger.RedPacketConfig{}, err
		}
		return config, nil
	}
	config.ExpireSeconds = ledger.RedPacketExpireSeconds
	counts, err := r.listRedPacketCountTiers(ctx, querier)
	if err != nil {
		return ledger.RedPacketConfig{}, err
	}
	amounts, err := r.listRedPacketAmountTiers(ctx, querier)
	if err != nil {
		return ledger.RedPacketConfig{}, err
	}
	config.CountTiers = counts
	config.AmountTiers = amounts
	return config, nil
}

func (r *Repository) listRedPacketCountTiers(ctx context.Context, querier redPacketQuerier) ([]int32, error) {
	rows, err := querier.QueryContext(ctx, `
		SELECT packet_count
		FROM red_packet_count_tiers
		WHERE app_code = ? AND status = 'active'
		ORDER BY sort_order DESC, packet_count ASC`,
		appcode.FromContext(ctx),
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := make([]int32, 0)
	for rows.Next() {
		var value int32
		if err := rows.Scan(&value); err != nil {
			return nil, err
		}
		items = append(items, value)
	}
	return items, rows.Err()
}

func (r *Repository) listRedPacketAmountTiers(ctx context.Context, querier redPacketQuerier) ([]int64, error) {
	rows, err := querier.QueryContext(ctx, `
		SELECT total_amount
		FROM red_packet_amount_tiers
		WHERE app_code = ? AND status = 'active'
		ORDER BY sort_order DESC, total_amount ASC`,
		appcode.FromContext(ctx),
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	items := make([]int64, 0)
	for rows.Next() {
		var value int64
		if err := rows.Scan(&value); err != nil {
			return nil, err
		}
		items = append(items, value)
	}
	return items, rows.Err()
}

func (r *Repository) incrementRedPacketDailyCounter(ctx context.Context, tx *sql.Tx, userID int64, limit int32, nowMS int64) error {
	if limit <= 0 {
		return xerr.New(xerr.RedPacketDailyLimitReached, "red packet daily limit reached")
	}
	day := time.UnixMilli(nowMS).UTC().Format("2006-01-02")
	var current int32
	err := tx.QueryRowContext(ctx, `
		SELECT sent_count
		FROM red_packet_user_daily_counters
		WHERE app_code = ? AND user_id = ? AND send_day = ?
		FOR UPDATE`,
		appcode.FromContext(ctx), userID, day,
	).Scan(&current)
	if errors.Is(err, sql.ErrNoRows) {
		_, err = tx.ExecContext(ctx, `
			INSERT INTO red_packet_user_daily_counters (
				app_code, user_id, send_day, sent_count, created_at_ms, updated_at_ms
			) VALUES (?, ?, ?, 1, ?, ?)`,
			appcode.FromContext(ctx), userID, day, nowMS, nowMS,
		)
		return err
	}
	if err != nil {
		return err
	}
	if current >= limit {
		return xerr.New(xerr.RedPacketDailyLimitReached, "red packet daily limit reached")
	}
	_, err = tx.ExecContext(ctx, `
		UPDATE red_packet_user_daily_counters
		SET sent_count = sent_count + 1, updated_at_ms = ?
		WHERE app_code = ? AND user_id = ? AND send_day = ?`,
		nowMS, appcode.FromContext(ctx), userID, day,
	)
	return err
}

func int64InSet(value int64, values []int64) bool {
	for _, item := range values {
		if item == value {
			return true
		}
	}
	return false
}

func int32InSet(value int32, values []int32) bool {
	for _, item := range values {
		if item == value {
			return true
		}
	}
	return false
}

func normalizeRedPacketConfig(config ledger.RedPacketConfig) ledger.RedPacketConfig {
	config.AppCode = appcode.Normalize(config.AppCode)
	config.CountTiers = normalizeRedPacketCountTiers(config.CountTiers)
	config.AmountTiers = normalizeRedPacketAmountTiers(config.AmountTiers)
	return config
}

func normalizeRedPacketCountTiers(values []int32) []int32 {
	seen := make(map[int32]bool, len(values))
	out := make([]int32, 0, len(values))
	for _, value := range values {
		if value <= 0 || seen[value] {
			continue
		}
		seen[value] = true
		out = append(out, value)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}

func normalizeRedPacketAmountTiers(values []int64) []int64 {
	seen := make(map[int64]bool, len(values))
	out := make([]int64, 0, len(values))
	for _, value := range values {
		if value <= 0 || seen[value] {
			continue
		}
		seen[value] = true
		out = append(out, value)
	}
	sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
	return out
}
