package mysql

import (
	"context"
	"database/sql"
	"errors"
	"math/big"
	"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) {
	giftTypeCodes := giftRatioTypeCandidates(giftTypeCode)
	regions := []int64{regionID}
	if regionID != 0 {
		regions = append(regions, 0)
	}
	for _, regionID := range regions {
		for _, candidate := range giftTypeCodes {
			ratio, exists, err := r.getGiftDiamondRatio(ctx, tx, appCode, regionID, candidate)
			if err != nil {
				return giftDiamondRatioSnapshot{}, 0, err
			}
			if exists {
				return ratio, regionID, nil
			}
		}
	}
	// 礼物类型倍率属于后台运行政策。VIP 礼物只继承同区域或全局已持久化的 normal 政策，
	// 其余类型仍在区域和全局都未配置时 fail-close，不能用编译期百分比制造 Host 收益。
	return giftDiamondRatioSnapshot{}, 0, xerr.New(xerr.PermissionDenied, "gift diamond ratio is not configured")
}

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 == "" {
		return giftDiamondRatioSnapshot{}, false, xerr.New(xerr.InvalidArgument, "gift diamond ratio is invalid")
	}
	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) {
	giftTypeCodes := giftRatioTypeCandidates(giftTypeCode)
	regions := []int64{regionID}
	if regionID != 0 {
		regions = append(regions, 0)
	}
	for _, regionID := range regions {
		for _, candidate := range giftTypeCodes {
			ratio, exists, err := r.getGiftReturnCoinRatio(ctx, tx, appCode, regionID, candidate)
			if err != nil {
				return giftDiamondRatioSnapshot{}, 0, err
			}
			if exists {
				return ratio, regionID, nil
			}
		}
	}
	// 返金币与主播钻石倍率共用后台配置行，但业务含义独立。VIP 礼物继承已持久化的
	// normal 返币政策；没有任何可继承政策时仍必须在账户锁和扣币前失败。
	return giftDiamondRatioSnapshot{}, 0, xerr.New(xerr.PermissionDenied, "gift return coin ratio is not configured")
}

// giftRatioTypeCandidates 将 VIP 身份门禁与礼物经济分类解耦：noble/vip 先读取运营可选的
// 专属倍率，缺失时继承 normal 的已发布倍率。这样新增 VIP 展示分类不会要求运营复制整套
// 区域政策，也不会绕过 fail-close，因为 normal 行缺失时仍然拒绝扣费。
func giftRatioTypeCandidates(giftTypeCode string) []string {
	normalized := resourcedomain.NormalizeGiftTypeCode(giftTypeCode)
	if resourcedomain.IsVIPGiftType(normalized) {
		return []string{normalized, resourcedomain.GiftTypeNormal}
	}
	return []string{normalized}
}

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 == "" {
		return giftDiamondRatioSnapshot{}, false, xerr.New(xerr.InvalidArgument, "gift return coin ratio is invalid")
	}
	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
}

// giftDiamondAmountWithPolicyCoefficient 在一次定点运算中完成
// 金币 * 礼物类型倍率 * Host 政策系数，最后统一向下取整。
// 不能先算礼物倍率再乘政策系数，否则两次整数截断会让小额礼物少记周期钻石。
func giftDiamondAmountWithPolicyCoefficient(chargeAmount int64, giftTypeRatioPPM int64, policyCoefficientPPM int64) (int64, error) {
	if chargeAmount < 0 || giftTypeRatioPPM < 0 || policyCoefficientPPM <= 0 {
		return 0, xerr.New(xerr.InvalidArgument, "host period diamond amount is invalid")
	}
	if chargeAmount == 0 || giftTypeRatioPPM == 0 {
		return 0, nil
	}

	// 系数允许 6 位小数，两个 ppm 分母合并为 10^12。big.Int 只用于防止合法大额
	// 在中间乘法溢出；最终值仍必须落入账本的 BIGINT 边界。
	amount := new(big.Int).SetInt64(chargeAmount)
	amount.Mul(amount, big.NewInt(giftTypeRatioPPM))
	amount.Mul(amount, big.NewInt(policyCoefficientPPM))
	amount.Quo(amount, big.NewInt(1_000_000_000_000))
	if !amount.IsInt64() {
		return 0, xerr.New(xerr.InvalidArgument, "host period diamond amount overflow")
	}
	return amount.Int64(), nil
}

func amountByBPS(amount int64, bps int32) (int64, error) {
	if amount < 0 || bps < 0 || bps > 10_000 {
		return 0, xerr.New(xerr.InvalidArgument, "income share bps is invalid")
	}
	if amount == 0 || bps == 0 {
		return 0, nil
	}
	value := new(big.Int).Mul(big.NewInt(amount), big.NewInt(int64(bps)))
	value.Quo(value, big.NewInt(10_000))
	if !value.IsInt64() {
		return 0, xerr.New(xerr.InvalidArgument, "income share amount overflow")
	}
	return value.Int64(), nil
}

type hostPeriodDiamondCoefficientSnapshot struct {
	PolicyID                uint64
	PolicyVersion           uint64
	PolicyType              string
	Value                   string
	PPM                     int64
	PointDiamondsPerUSD     int64
	CoinsPerUSD             int64
	MinimumWithdrawUSDMinor int64
	WithdrawFeeBPS          int32
	AgencyPointShareBPS     int32
}

// resolveHostPolicyDiamondCoefficient 只读取主播区域在当前周期绑定的已发布 Host 政策。
// 当前月缺少显式绑定时仅继承紧邻上月；完全没有政策时返回 found=false，调用方不得用代码默认系数累计周期钻石。
func (r *Repository) resolveHostPolicyDiamondCoefficient(ctx context.Context, tx *sql.Tx, regionID int64, cycleKey string) (hostPeriodDiamondCoefficientSnapshot, bool, error) {
	query := func() (hostPeriodDiamondCoefficientSnapshot, bool, error) {
		var snapshot hostPeriodDiamondCoefficientSnapshot
		err := tx.QueryRowContext(ctx, `
			SELECT p.policy_id, p.policy_version, p.policy_type,
			       CAST(p.gift_coin_to_diamond_ratio AS CHAR),
			       CAST(ROUND(p.gift_coin_to_diamond_ratio * 1000000) AS SIGNED),
			       p.point_diamonds_per_usd, p.coins_per_usd, p.minimum_withdraw_usd_minor, p.withdraw_fee_bps,
			       p.agency_point_share_bps
			FROM host_salary_policy_cycle_bindings binding
			JOIN host_agency_salary_policies p
			  ON p.app_code = binding.app_code
			 AND p.policy_id = binding.policy_id
			 AND p.policy_version = binding.policy_version
			WHERE binding.app_code = ?
			  AND binding.region_id = ?
			  AND binding.cycle_key = ?
			  AND p.status = 'active'
			LIMIT 1`, appcode.FromContext(ctx), regionID, cycleKey).Scan(
			&snapshot.PolicyID, &snapshot.PolicyVersion, &snapshot.PolicyType, &snapshot.Value, &snapshot.PPM,
			&snapshot.PointDiamondsPerUSD, &snapshot.CoinsPerUSD, &snapshot.MinimumWithdrawUSDMinor, &snapshot.WithdrawFeeBPS,
			&snapshot.AgencyPointShareBPS,
		)
		if errors.Is(err, sql.ErrNoRows) {
			return hostPeriodDiamondCoefficientSnapshot{}, false, nil
		}
		if err != nil {
			return hostPeriodDiamondCoefficientSnapshot{}, false, err
		}
		if snapshot.PPM <= 0 {
			return hostPeriodDiamondCoefficientSnapshot{}, false, xerr.New(xerr.InvalidArgument, "host policy diamond coefficient is invalid")
		}
		snapshot.PolicyType = strings.ToUpper(strings.TrimSpace(snapshot.PolicyType))
		if snapshot.PolicyType != ledger.HostPolicyTypeSalaryDiamond && snapshot.PolicyType != ledger.HostPolicyTypePointDiamond {
			return hostPeriodDiamondCoefficientSnapshot{}, false, xerr.New(xerr.InvalidArgument, "host policy type is invalid")
		}
		if snapshot.AgencyPointShareBPS < 0 || snapshot.AgencyPointShareBPS > 10_000 {
			return hostPeriodDiamondCoefficientSnapshot{}, false, xerr.New(xerr.InvalidArgument, "agency point share bps is invalid")
		}
		snapshot.Value = strings.TrimSpace(snapshot.Value)
		return snapshot, true, nil
	}

	snapshot, found, err := query()
	if err != nil || found {
		return snapshot, found, err
	}
	previousCycleKey, err := previousHostSalaryCycleKey(cycleKey)
	if err != nil {
		return hostPeriodDiamondCoefficientSnapshot{}, false, nil
	}
	if err := r.inheritHostSalaryPolicyBinding(ctx, tx, regionID, previousCycleKey, cycleKey, "", ""); err != nil {
		return hostPeriodDiamondCoefficientSnapshot{}, false, err
	}
	return query()
}

// resolveHostPeriodDiamondCoefficient 只返回后台发布并绑定的 Host 政策。缺失政策时不产生任何主播钻石收益，
// 从而保证运行口径完全来自政策快照，而不是产品名或代码默认值。
func (r *Repository) resolveHostPeriodDiamondCoefficient(ctx context.Context, tx *sql.Tx, regionID int64, cycleKey string) (hostPeriodDiamondCoefficientSnapshot, bool, error) {
	return r.resolveHostPolicyDiamondCoefficient(ctx, tx, regionID, cycleKey)
}

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 DESC, 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
}
