package mysql

import (
	"context"
	"fmt"
	"strings"

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

// GetTeamHostSalaryStats 按 Agency 收款人集合聚合主播工资统计，经理中心团队工资卡片使用。
// 预收入口径与结算保持同一套档位数学：当前累计钻石命中的档位累计工资 + 超出档位的剩余钻石按月底费率折美元；
// 再与已结算累计（含已入账剩余折算）取大，保证已 month_end 关闭的周期回读即真实发放值，进行中周期是即时估算。
// 这是只读聚合，不加锁不推进任何结算进度。
func (r *Repository) GetTeamHostSalaryStats(ctx context.Context, query ledger.TeamHostSalaryStatsQuery) ([]ledger.TeamHostSalaryCycleStat, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
	}
	owners := uniquePositiveInt64(query.AgencyOwnerUserIDs)
	stats := make([]ledger.TeamHostSalaryCycleStat, 0, len(query.CycleKeys))
	// 同一区域不同月份可以绑定不同不可变版本；缓存键必须含 cycle_key，禁止把新月份政策套到历史周期。
	policyByRegionCycle := map[string]*ledger.HostSalaryPolicy{}
	for _, cycleKey := range query.CycleKeys {
		cycleKey = strings.TrimSpace(cycleKey)
		if cycleKey == "" {
			return nil, xerr.New(xerr.InvalidArgument, "cycle_key is required")
		}
		stat := ledger.TeamHostSalaryCycleStat{CycleKey: cycleKey}
		if len(owners) == 0 {
			// 经理团队还没有任何 Agency 时返回零值统计，调用方不需要为空团队做特殊分支。
			stats = append(stats, stat)
			continue
		}
		if err := r.aggregateTeamHostSalaryCycle(ctx, owners, cycleKey, policyByRegionCycle, &stat); err != nil {
			return nil, err
		}
		stats = append(stats, stat)
	}
	return stats, nil
}

// teamHostSalaryRow 是单主播周期账户与结算进度的联合投影，先整批读出再做档位估算。
type teamHostSalaryRow struct {
	RegionID                int64
	TotalDiamonds           int64
	SettledHostUSDMinor     int64
	SettledResidualUSDMinor int64
	MonthEndClearedAtMS     int64
}

func (r *Repository) aggregateTeamHostSalaryCycle(ctx context.Context, owners []int64, cycleKey string, policyByRegionCycle map[string]*ledger.HostSalaryPolicy, stat *ledger.TeamHostSalaryCycleStat) error {
	// 先把整个结果集读完再解析政策：政策查询和主查询共用连接池，游标未关时嵌套发查询
	// 在池被打满的场景下会互相等待造成饥饿；行结构很小，整批驻留内存成本可忽略。
	hostRows, err := r.listTeamHostSalaryRows(ctx, owners, cycleKey)
	if err != nil {
		return err
	}
	for _, row := range hostRows {
		// 已结算累计包含月底剩余钻石折美元，两者都入了主播工资钱包，属于同一“主播工资”口径。
		settledTotal, err := checkedAdd(row.SettledHostUSDMinor, row.SettledResidualUSDMinor)
		if err != nil {
			return err
		}
		estimated := settledTotal
		// month_end 已清算的周期以真实发放为准；未关闭周期也只能用自己 cycle_key 已绑定的版本估算。
		if row.MonthEndClearedAtMS == 0 {
			policy, err := r.resolveTeamSalaryPolicy(ctx, row.RegionID, cycleKey, policyByRegionCycle)
			if err != nil {
				return err
			}
			if policy != nil {
				// 与结算同一套档位数学：最高已达档位累计工资 + 档位之上的剩余钻石按月底费率折美元。
				level := highestHostSalaryLevel(policy.Levels, row.TotalDiamonds)
				residualEstimate, err := usdMinorFromDiamondRate(residualDiamondRemainder(row.TotalDiamonds, level), policy.ResidualDiamondToUSDRate)
				if err != nil {
					return err
				}
				levelTotal, err := checkedAdd(level.HostSalaryUSDMinor, residualEstimate)
				if err != nil {
					return err
				}
				// 取大兜底政策回调：进行中周期的预收入永远不小于已经真实入账的工资。
				estimated = maxInt64(settledTotal, levelTotal)
			}
		}
		stat.ActiveHostCount++
		stat.TotalDiamonds, err = checkedAdd(stat.TotalDiamonds, row.TotalDiamonds)
		if err != nil {
			return err
		}
		stat.SettledHostSalaryUSDMinor, err = checkedAdd(stat.SettledHostSalaryUSDMinor, settledTotal)
		if err != nil {
			return err
		}
		stat.EstimatedHostSalaryUSDMinor, err = checkedAdd(stat.EstimatedHostSalaryUSDMinor, estimated)
		if err != nil {
			return err
		}
	}
	return nil
}

func (r *Repository) listTeamHostSalaryRows(ctx context.Context, owners []int64, cycleKey string) ([]teamHostSalaryRow, error) {
	args := make([]any, 0, len(owners)+2)
	args = append(args, appcode.FromContext(ctx), cycleKey)
	for _, owner := range owners {
		args = append(args, owner)
	}
	// 周期账户是收礼实时累计的事实表；LEFT JOIN 进度表拿到已结算累计和周期关闭标记，未结算过的主播按 0 处理。
	rows, err := r.db.QueryContext(ctx, `
		SELECT a.region_id, a.total_diamonds,
		       COALESCE(p.settled_host_salary_usd_minor, 0),
		       COALESCE(p.residual_usd_minor, 0),
		       COALESCE(p.month_end_cleared_at_ms, 0)
		FROM host_period_diamond_accounts a
		LEFT JOIN host_salary_settlement_progress p
		  ON p.app_code = a.app_code AND p.user_id = a.user_id AND p.cycle_key = a.cycle_key
		WHERE a.app_code = ?
		  AND a.cycle_key = ?
		  AND a.total_diamonds > 0
		  AND a.agency_owner_user_id IN (`+placeholders(len(owners))+`)`,
		args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	hostRows := make([]teamHostSalaryRow, 0, 256)
	for rows.Next() {
		var row teamHostSalaryRow
		if err := rows.Scan(&row.RegionID, &row.TotalDiamonds, &row.SettledHostUSDMinor, &row.SettledResidualUSDMinor, &row.MonthEndClearedAtMS); err != nil {
			return nil, err
		}
		hostRows = append(hostRows, row)
	}
	return hostRows, rows.Err()
}

func (r *Repository) resolveTeamSalaryPolicy(ctx context.Context, regionID int64, cycleKey string, policyByRegionCycle map[string]*ledger.HostSalaryPolicy) (*ledger.HostSalaryPolicy, error) {
	cacheKey := fmt.Sprintf("%d:%s", regionID, cycleKey)
	if cached, ok := policyByRegionCycle[cacheKey]; ok {
		return cached, nil
	}
	// 展示估算不区分结算模式和触发方式，但仍必须沿周期绑定读取精确版本。
	policy, found, err := r.queryHostSalaryPolicyByCycle(ctx, r.db, regionID, cycleKey, "", "")
	if err != nil {
		return nil, err
	}
	if !found {
		// 区域没有生效政策时记 nil，主播预收入退化为已结算累计，不做档位估算。
		policyByRegionCycle[cacheKey] = nil
		return nil, nil
	}
	if policy.PolicyType != ledger.HostPolicyTypeSalaryDiamond {
		// 永久钻石积分不会生成周期工资统计；同一团队页面只保留 SALARY_DIAMOND 的工资口径。
		policyByRegionCycle[cacheKey] = nil
		return nil, nil
	}
	policyByRegionCycle[cacheKey] = &policy
	return &policy, nil
}
