package mysql

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

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
	resourcedomain "hyapp/services/wallet-service/internal/domain/resource"
)

type giftPrice struct {
	GiftID          string
	PriceVersion    string
	ChargeAssetType string
	CoinPrice       int64
	// GiftPointAmount 是 wallet_gift_prices 的历史列；送礼结算只按 CoinPrice 和比例计算，不再读取它做收益。
	GiftPointAmount int64
	HeatValue       int64
}

type rechargePolicy struct {
	PolicyID        int64
	RegionID        int64
	PolicyVersion   string
	CurrencyCode    string
	CoinAmount      int64
	USDMinorAmount  int64
	EffectiveFromMs int64
}

type giftDiamondRatioSnapshot struct {
	Percent string
	PPM     int64
}

func (r *Repository) resolveGiftPrice(ctx context.Context, tx *sql.Tx, giftID string, priceVersion string, nowMs int64) (giftPrice, error) {
	var row *sql.Row
	if strings.TrimSpace(priceVersion) != "" {
		row = tx.QueryRowContext(ctx,
			`SELECT gift_id, price_version, COALESCE(charge_asset_type, 'COIN'), coin_price, gift_point_amount, heat_value
			 FROM wallet_gift_prices
			 WHERE app_code = ? AND gift_id = ? AND price_version = ? AND status = 'active' AND effective_at_ms <= ?`,
			appcode.FromContext(ctx), giftID, priceVersion, nowMs,
		)
	} else {
		row = tx.QueryRowContext(ctx,
			`SELECT gift_id, price_version, COALESCE(charge_asset_type, 'COIN'), coin_price, gift_point_amount, heat_value
			 FROM wallet_gift_prices
			 WHERE app_code = ? AND gift_id = ? AND status = 'active' AND effective_at_ms <= ?
			 ORDER BY effective_at_ms DESC, price_version DESC
			 LIMIT 1`,
			appcode.FromContext(ctx), giftID, nowMs,
		)
	}

	var price giftPrice
	if err := row.Scan(&price.GiftID, &price.PriceVersion, &price.ChargeAssetType, &price.CoinPrice, &price.GiftPointAmount, &price.HeatValue); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return giftPrice{}, xerr.New(xerr.NotFound, "gift price is not active")
		}
		return giftPrice{}, err
	}
	if price.CoinPrice < 0 || price.HeatValue < 0 {
		return giftPrice{}, xerr.New(xerr.Internal, "gift price is invalid")
	}
	price.ChargeAssetType = ledger.NormalizeGiftChargeAssetType(price.ChargeAssetType)
	return price, nil
}

func (r *Repository) resolveGiftDiamondRatio(ctx context.Context, tx *sql.Tx, appCode string, regionID int64, giftTypeCode string) (giftDiamondRatioSnapshot, int64, error) {
	giftTypeCode = strings.TrimSpace(giftTypeCode)
	if giftTypeCode == "" {
		giftTypeCode = "normal"
	}
	regions := []int64{regionID}
	if regionID != 0 {
		regions = append(regions, 0)
	}
	for _, regionID := range regions {
		ratio, exists, err := r.getGiftDiamondRatio(ctx, tx, appCode, regionID, giftTypeCode)
		if err != nil {
			return giftDiamondRatioSnapshot{}, 0, err
		}
		if exists {
			return ratio, regionID, nil
		}
	}
	return defaultGiftDiamondRatio(giftTypeCode), 0, nil
}

func (r *Repository) resolveGlobalGiftDiamondRatio(ctx context.Context, tx *sql.Tx, appCode string, giftTypeCode string) (giftDiamondRatioSnapshot, int64, error) {

	return r.resolveGiftDiamondRatio(ctx, tx, appCode, 0, giftTypeCode)
}

func (r *Repository) getGiftDiamondRatio(ctx context.Context, tx *sql.Tx, appCode string, regionID int64, giftTypeCode string) (giftDiamondRatioSnapshot, bool, error) {
	var percent string
	var ppm int64
	err := tx.QueryRowContext(ctx, `
		SELECT CAST(ratio_percent AS CHAR), CAST(ROUND(ratio_percent * 10000) AS SIGNED)
		FROM gift_diamond_ratio_configs
		WHERE app_code = ? AND region_id = ? AND gift_type_code = ? AND status = 'active'
		LIMIT 1`,
		appcode.Normalize(appCode), regionID, giftTypeCode,
	).Scan(&percent, &ppm)
	if errors.Is(err, sql.ErrNoRows) {
		return giftDiamondRatioSnapshot{}, false, nil
	}
	if err != nil {
		return giftDiamondRatioSnapshot{}, false, err
	}
	if ppm < 0 || ppm > 1_000_000 {
		return giftDiamondRatioSnapshot{}, false, xerr.New(xerr.InvalidArgument, "gift diamond ratio is invalid")
	}
	percent = strings.TrimSpace(percent)
	if percent == "" {
		percent = "100.00"
	}
	return giftDiamondRatioSnapshot{Percent: percent, PPM: ppm}, true, nil
}

func (r *Repository) resolveGiftReturnCoinRatio(ctx context.Context, tx *sql.Tx, appCode string, regionID int64, giftTypeCode string) (giftDiamondRatioSnapshot, int64, error) {
	giftTypeCode = strings.TrimSpace(giftTypeCode)
	if giftTypeCode == "" {
		giftTypeCode = "normal"
	}
	regions := []int64{regionID}
	if regionID != 0 {
		regions = append(regions, 0)
	}
	for _, regionID := range regions {
		ratio, exists, err := r.getGiftReturnCoinRatio(ctx, tx, appCode, regionID, giftTypeCode)
		if err != nil {
			return giftDiamondRatioSnapshot{}, 0, err
		}
		if exists {
			return ratio, regionID, nil
		}
	}
	return defaultGiftReturnCoinRatio(giftTypeCode), 0, nil
}

func (r *Repository) getGiftReturnCoinRatio(ctx context.Context, tx *sql.Tx, appCode string, regionID int64, giftTypeCode string) (giftDiamondRatioSnapshot, bool, error) {
	var percent string
	var ppm int64
	err := tx.QueryRowContext(ctx, `
		SELECT CAST(coin_return_ratio_percent AS CHAR), CAST(ROUND(coin_return_ratio_percent * 10000) AS SIGNED)
		FROM gift_diamond_ratio_configs
		WHERE app_code = ? AND region_id = ? AND gift_type_code = ? AND status = 'active'
		LIMIT 1`,
		appcode.Normalize(appCode), regionID, giftTypeCode,
	).Scan(&percent, &ppm)
	if errors.Is(err, sql.ErrNoRows) {
		return giftDiamondRatioSnapshot{}, false, nil
	}
	if err != nil {
		return giftDiamondRatioSnapshot{}, false, err
	}
	if ppm < 0 || ppm > 1_000_000 {
		return giftDiamondRatioSnapshot{}, false, xerr.New(xerr.InvalidArgument, "gift return coin ratio is invalid")
	}
	percent = strings.TrimSpace(percent)
	if percent == "" {
		percent = defaultGiftReturnCoinRatio(giftTypeCode).Percent
	}
	return giftDiamondRatioSnapshot{Percent: percent, PPM: ppm}, true, nil
}

func giftDiamondAmount(chargeAmount int64, ratioPPM int64) (int64, error) {
	if chargeAmount < 0 || ratioPPM < 0 {
		return 0, xerr.New(xerr.InvalidArgument, "gift diamond amount is invalid")
	}
	if chargeAmount == 0 || ratioPPM == 0 {
		// 0 金额或 0% 比例都是合法业务结果；这里只跳过乘法溢出工具的正数乘数约束，不放宽负数和数量校验。
		return 0, nil
	}
	product, err := checkedMul(chargeAmount, ratioPPM)
	if err != nil {
		return 0, err
	}
	return product / 1_000_000, nil
}

func defaultGiftDiamondRatio(giftTypeCode string) giftDiamondRatioSnapshot {
	switch strings.TrimSpace(giftTypeCode) {
	case "lucky":
		return giftDiamondRatioSnapshot{Percent: "10.00", PPM: 100_000}
	case "super_lucky":
		return giftDiamondRatioSnapshot{Percent: "1.00", PPM: 10_000}
	default:
		return giftDiamondRatioSnapshot{Percent: "100.00", PPM: 1_000_000}
	}
}

func defaultGiftReturnCoinRatio(giftTypeCode string) giftDiamondRatioSnapshot {
	switch strings.TrimSpace(giftTypeCode) {
	case resourcedomain.GiftTypeLucky:
		return giftDiamondRatioSnapshot{Percent: "10.00", PPM: 100_000}
	case resourcedomain.GiftTypeSuperLucky:
		return giftDiamondRatioSnapshot{Percent: "1.00", PPM: 10_000}
	default:
		return giftDiamondRatioSnapshot{Percent: "30.00", PPM: 300_000}
	}
}

func (r *Repository) resolveRechargePolicy(ctx context.Context, tx *sql.Tx, regionID int64, nowMs int64) (rechargePolicy, error) {
	row := tx.QueryRowContext(ctx,
		`SELECT policy_id, region_id, policy_version, currency_code, coin_amount, usd_minor_amount, effective_from_ms
		 FROM wallet_recharge_policies
		 WHERE app_code = ? AND region_id = ? AND status = 'active'
		   AND effective_from_ms <= ?
		   AND (effective_to_ms IS NULL OR effective_to_ms > ?)
		 ORDER BY effective_from_ms DESC, policy_id DESC
		 LIMIT 1`,
		appcode.FromContext(ctx),
		regionID,
		nowMs,
		nowMs,
	)
	var policy rechargePolicy
	if err := row.Scan(&policy.PolicyID, &policy.RegionID, &policy.PolicyVersion, &policy.CurrencyCode, &policy.CoinAmount, &policy.USDMinorAmount, &policy.EffectiveFromMs); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return rechargePolicy{}, xerr.New(xerr.NotFound, "recharge policy not found")
		}
		return rechargePolicy{}, err
	}
	if policy.CoinAmount <= 0 || policy.USDMinorAmount <= 0 || strings.TrimSpace(policy.CurrencyCode) == "" {
		return rechargePolicy{}, xerr.New(xerr.Internal, "recharge policy is invalid")
	}
	return policy, nil
}

func calculateRechargeUSDMinor(coinAmount int64, policy rechargePolicy) (int64, error) {
	numerator, err := checkedMul(coinAmount, policy.USDMinorAmount)
	if err != nil {
		return 0, err
	}

	return numerator / policy.CoinAmount, nil
}

func (r *Repository) resolveCoinSellerSalaryExchangeRateTier(ctx context.Context, tx *sql.Tx, regionID int64, salaryUSDMinor int64) (ledger.CoinSellerSalaryExchangeRateTier, error) {
	row := tx.QueryRowContext(ctx,
		`SELECT region_id, min_usd_minor, max_usd_minor, coin_per_usd, status, sort_order, updated_at_ms
		 FROM coin_seller_salary_exchange_rate_tiers
		 WHERE app_code = ? AND region_id = ? AND status = 'active'
		   AND min_usd_minor <= ? AND max_usd_minor >= ?
		 ORDER BY sort_order ASC, min_usd_minor ASC, tier_id ASC
		 LIMIT 1
		 FOR UPDATE`,
		appcode.FromContext(ctx),
		regionID,
		salaryUSDMinor,
		salaryUSDMinor,
	)
	var tier ledger.CoinSellerSalaryExchangeRateTier
	if err := row.Scan(&tier.RegionID, &tier.MinUSDMinor, &tier.MaxUSDMinor, &tier.CoinPerUSD, &tier.Status, &tier.SortOrder, &tier.UpdatedAtMS); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return ledger.CoinSellerSalaryExchangeRateTier{}, xerr.New(xerr.NotFound, "exchange rate not configured")
		}
		return ledger.CoinSellerSalaryExchangeRateTier{}, err
	}
	if tier.MinUSDMinor <= 0 || tier.MaxUSDMinor < tier.MinUSDMinor || tier.CoinPerUSD <= 0 || tier.CoinPerUSD%100 != 0 {

		return ledger.CoinSellerSalaryExchangeRateTier{}, xerr.New(xerr.Internal, "coin seller salary exchange rate is invalid")
	}
	return tier, nil
}

func calculateSalaryCoinAmount(salaryUSDMinor int64, coinPerUSD int64) (int64, error) {
	numerator, err := checkedMul(salaryUSDMinor, coinPerUSD)
	if err != nil {
		return 0, err
	}
	if numerator%100 != 0 {
		return 0, xerr.New(xerr.InvalidArgument, "salary amount does not match exchange rate")
	}
	return numerator / 100, nil
}
