package databi

import (
	"context"
	"sort"
	"strconv"
	"strings"

	"hyapp-admin-server/internal/modules/payment"
)

const financeRechargeTZOffsetMinutes int32 = 480

type financeRechargeValues struct {
	GoogleUSDMinor     int64
	ThirdPartyUSDMinor int64
	CoinSellerUSDMinor int64
}

func (v financeRechargeValues) userUSDMinor() int64 {
	return v.GoogleUSDMinor + v.ThirdPartyUSDMinor
}

func (v financeRechargeValues) totalUSDMinor() int64 {
	return v.userUSDMinor() + v.CoinSellerUSDMinor
}

func (s *Service) applyFinanceRechargeOverview(
	ctx context.Context,
	out *AppOverview,
	query OverviewQuery,
	regions []RegionInfo,
	restricted bool,
	allowedRegionIDs []int64,
) error {
	if s == nil || s.financeRecharge == nil || out == nil {
		return nil
	}
	scopeRegionIDs := []int64(nil)
	if query.RegionID > 0 {
		scopeRegionIDs = []int64{normalizeRegionID(regions, query.RegionID)}
	} else if len(query.RegionIDs) > 0 {
		scopeRegionIDs = normalizeRegionIDs(regions, query.RegionIDs)
	} else if restricted {
		scopeRegionIDs = normalizeRegionIDs(regions, allowedRegionIDs)
	}

	current, err := s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{
		AppCode:         out.AppCode,
		StartAtMS:       query.StartMS,
		EndAtMS:         query.EndMS,
		ScopeRegionIDs:  scopeRegionIDs,
		IncludeRegions:  true,
		TZOffsetMinutes: financeRechargeTZOffsetMinutes,
	})
	if err != nil {
		return err
	}

	var previous payment.FinanceRechargeOverview
	if query.StartMS > 0 && query.EndMS > query.StartMS {
		periodLength := query.EndMS - query.StartMS
		previous, err = s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{
			AppCode:         out.AppCode,
			StartAtMS:       query.StartMS - periodLength,
			EndAtMS:         query.StartMS,
			ScopeRegionIDs:  scopeRegionIDs,
			IncludeRegions:  false,
			TZOffsetMinutes: financeRechargeTZOffsetMinutes,
		})
		if err != nil {
			return err
		}
	}

	currentTotal := financeRechargeOverviewTotal(current)
	previousTotal := financeRechargeOverviewTotal(previous)
	applyFinanceRechargeValues(out.Total, currentTotal)
	if query.StartMS > 0 && query.EndMS > query.StartMS {
		out.Total["recharge_delta_rate"] = financeRechargeDelta(currentTotal.totalUSDMinor(), previousTotal.totalUSDMinor())
		out.Total["user_recharge_delta_rate"] = financeRechargeDelta(currentTotal.userUSDMinor(), previousTotal.userUSDMinor())
	}
	// Finance 没有付费用户人数口径；金额切源后旧 ARPU/ARPPU 环比不可继续展示，否则分子来自旧 Dashboard。
	out.Total["arpu_delta_rate"] = nil
	out.Total["arppu_delta_rate"] = nil

	out.DailySeries = overlayFinanceRechargeDaily(out.DailySeries, current.Daily)
	out.RegionBreakdown, out.DailyRegionBreakdown = overlayFinanceRechargeRegions(
		out.RegionBreakdown,
		out.DailyRegionBreakdown,
		current.Regions,
		regions,
	)
	// Finance 页面没有国家维度，国家充值必须整体标为不可用；保留任何旧 Dashboard 金额都会导致
	// App/区域合计与国家合计无法对账，并与下发的 Finance 指标来源说明矛盾。
	markFinanceRechargeUnavailable(out.CountryBreakdown)
	markFinanceRechargeUnavailable(out.DailyCountryBreakdown)
	out.ReportMetricSources = appendFinanceRechargeMetricSources(out.ReportMetricSources)
	return nil
}

func (s *Service) applyFinanceRechargeKpiWindow(
	ctx context.Context,
	appCode string,
	startMS int64,
	endMS int64,
	total map[string]any,
	regionMetrics map[int64]map[string]any,
) error {
	financeOverview, err := s.financeRecharge.FinanceRechargeOverview(ctx, payment.FinanceRechargeOverviewQuery{
		AppCode:         appCode,
		StartAtMS:       startMS,
		EndAtMS:         endMS,
		IncludeRegions:  true,
		TZOffsetMinutes: financeRechargeTZOffsetMinutes,
	})
	if err != nil {
		return err
	}
	applyFinanceRechargeValues(total, financeRechargeOverviewTotal(financeOverview))
	for regionID, row := range regionMetrics {
		zeroFinanceRecharge([]map[string]any{row})
		regionMetrics[regionID] = row
	}
	for _, financeRegion := range financeOverview.Regions {
		row := regionMetrics[financeRegion.RegionID]
		if row == nil {
			row = map[string]any{"region_id": financeRegion.RegionID}
			regionMetrics[financeRegion.RegionID] = row
		}
		// KPI 只消费区域总充值；Finance 区域分布没有渠道拆分，不能用 0 伪造各渠道。
		applyFinanceRechargeTotalOnly(row, financeRegion.TotalUSDMinor)
	}
	return nil
}

func financeRechargeOverviewTotal(overview payment.FinanceRechargeOverview) financeRechargeValues {
	var total financeRechargeValues
	for _, row := range overview.Daily {
		total.GoogleUSDMinor += row.GoogleUSDMinor
		total.ThirdPartyUSDMinor += row.ThirdPartyUSDMinor
		total.CoinSellerUSDMinor += row.CoinSellerUSDMinor
	}
	return total
}

func financeRechargeValuesFromDaily(row payment.FinanceRechargeDailyBucket) financeRechargeValues {
	return financeRechargeValues{
		GoogleUSDMinor:     row.GoogleUSDMinor,
		ThirdPartyUSDMinor: row.ThirdPartyUSDMinor,
		CoinSellerUSDMinor: row.CoinSellerUSDMinor,
	}
}

func applyFinanceRechargeValues(row map[string]any, values financeRechargeValues) {
	if row == nil {
		return
	}
	row["google_recharge_usd_minor"] = values.GoogleUSDMinor
	// Databi 历史字段名是 mifapay，但 Finance 的 thirdParty 包含全部非 Google 三方渠道。
	row["mifapay_recharge_usd_minor"] = values.ThirdPartyUSDMinor
	row["third_party_recharge_usd_minor"] = values.ThirdPartyUSDMinor
	row["coin_seller_recharge_usd_minor"] = values.CoinSellerUSDMinor
	row["user_recharge_usd_minor"] = values.userUSDMinor()
	row["recharge_usd_minor"] = values.totalUSDMinor()
	row["total_recharge_usd_minor"] = values.totalUSDMinor()
	row["new_user_recharge_usd_minor"] = nil
	markFinanceRechargeDerivedUnavailable(row)
}

func applyFinanceRechargeTotalOnly(row map[string]any, totalUSDMinor int64) {
	if row == nil {
		return
	}
	markFinanceRechargeUnavailable([]map[string]any{row})
	row["recharge_usd_minor"] = totalUSDMinor
	row["total_recharge_usd_minor"] = totalUSDMinor
}

func markFinanceRechargeDerivedUnavailable(row map[string]any) {
	if row == nil {
		return
	}
	// Finance 的币商/H5 金额没有与旧 Dashboard paid_users 一致的去重人数，
	// 因此 ARPU/ARPPU 不做跨来源除法；前端通过该标记在跨 App 聚合时继续保持缺失。
	row["finance_recharge_aligned"] = true
	row["arpu_usd_minor"] = nil
	row["arppu_usd_minor"] = nil
}

func overlayFinanceRechargeDaily(existing []map[string]any, finance []payment.FinanceRechargeDailyBucket) []map[string]any {
	byDay := make(map[string]payment.FinanceRechargeDailyBucket, len(finance))
	for _, row := range finance {
		if row.Date != "" {
			byDay[row.Date] = row
		}
	}
	seen := map[string]struct{}{}
	for _, row := range existing {
		day := rowString(row, "stat_day", "label")
		bucket := byDay[day]
		applyFinanceRechargeValues(row, financeRechargeValuesFromDaily(bucket))
		if day != "" {
			seen[day] = struct{}{}
		}
	}
	for day, bucket := range byDay {
		if _, ok := seen[day]; ok {
			continue
		}
		row := map[string]any{"label": day, "stat_day": day}
		applyFinanceRechargeValues(row, financeRechargeValuesFromDaily(bucket))
		existing = append(existing, row)
	}
	sort.Slice(existing, func(i, j int) bool {
		return rowString(existing[i], "stat_day", "label") < rowString(existing[j], "stat_day", "label")
	})
	return existing
}

func overlayFinanceRechargeRegions(
	regionRows []map[string]any,
	dailyRegionRows []map[string]any,
	financeRegions []payment.FinanceRechargeRegionOverview,
	regions []RegionInfo,
) ([]map[string]any, []map[string]any) {
	zeroFinanceRecharge(regionRows)
	dailyAvailable := false
	for _, financeRegion := range financeRegions {
		if financeRegion.DailyAvailable {
			dailyAvailable = true
			break
		}
	}
	if dailyAvailable {
		zeroFinanceRecharge(dailyRegionRows)
	} else {
		// 默认全量查询只复用 Finance 已有的区域总额；逐日渠道仅在用户显式选择区域时查询。
		// 这里用 null 清掉旧 Dashboard 值，避免“未查询”被展示成 0 或旧口径。
		markFinanceRechargeUnavailable(dailyRegionRows)
	}
	regionIndex := map[int64]map[string]any{}
	for _, row := range regionRows {
		if regionID, ok := rowInt64(row, "region_id", "regionId"); ok {
			regionIndex[normalizeRegionID(regions, regionID)] = row
		}
	}
	dailyIndex := map[string]map[string]any{}
	for _, row := range dailyRegionRows {
		regionID, ok := rowInt64(row, "region_id", "regionId")
		if !ok {
			continue
		}
		day := rowString(row, "stat_day", "label")
		dailyIndex[financeRegionDayKey(normalizeRegionID(regions, regionID), day)] = row
	}

	for _, financeRegion := range financeRegions {
		regionID := normalizeRegionID(regions, financeRegion.RegionID)
		row := regionIndex[regionID]
		if row == nil {
			code, name := regionDisplay(regions, regionID)
			row = map[string]any{"region_id": regionID, "region_code": code, "region_name": name}
			regionRows = append(regionRows, row)
			regionIndex[regionID] = row
		}
		var total financeRechargeValues
		for _, dayBucket := range financeRegion.Daily {
			values := financeRechargeValuesFromDaily(dayBucket)
			total.GoogleUSDMinor += values.GoogleUSDMinor
			total.ThirdPartyUSDMinor += values.ThirdPartyUSDMinor
			total.CoinSellerUSDMinor += values.CoinSellerUSDMinor
			key := financeRegionDayKey(regionID, dayBucket.Date)
			dailyRow := dailyIndex[key]
			if dailyRow == nil {
				code, name := regionDisplay(regions, regionID)
				dailyRow = map[string]any{
					"label":       dayBucket.Date,
					"stat_day":    dayBucket.Date,
					"region_id":   regionID,
					"region_code": code,
					"region_name": name,
				}
				dailyRegionRows = append(dailyRegionRows, dailyRow)
				dailyIndex[key] = dailyRow
			}
			applyFinanceRechargeValues(dailyRow, values)
		}
		if financeRegion.DailyAvailable {
			applyFinanceRechargeValues(row, total)
		} else {
			applyFinanceRechargeTotalOnly(row, financeRegion.TotalUSDMinor)
		}
	}
	sort.Slice(regionRows, func(i, j int) bool {
		left, _ := rowInt64(regionRows[i], "region_id", "regionId")
		right, _ := rowInt64(regionRows[j], "region_id", "regionId")
		return left < right
	})
	sort.Slice(dailyRegionRows, func(i, j int) bool {
		leftDay := rowString(dailyRegionRows[i], "stat_day", "label")
		rightDay := rowString(dailyRegionRows[j], "stat_day", "label")
		if leftDay != rightDay {
			return leftDay < rightDay
		}
		left, _ := rowInt64(dailyRegionRows[i], "region_id", "regionId")
		right, _ := rowInt64(dailyRegionRows[j], "region_id", "regionId")
		return left < right
	})
	return regionRows, dailyRegionRows
}

func zeroFinanceRecharge(rows []map[string]any) {
	for _, row := range rows {
		applyFinanceRechargeValues(row, financeRechargeValues{})
	}
}

func markFinanceRechargeUnavailable(rows []map[string]any) {
	for _, row := range rows {
		row["google_recharge_usd_minor"] = nil
		row["mifapay_recharge_usd_minor"] = nil
		row["third_party_recharge_usd_minor"] = nil
		row["coin_seller_recharge_usd_minor"] = nil
		row["user_recharge_usd_minor"] = nil
		row["recharge_usd_minor"] = nil
		row["total_recharge_usd_minor"] = nil
		row["new_user_recharge_usd_minor"] = nil
		markFinanceRechargeDerivedUnavailable(row)
	}
}

func requirementsIncludesRevenue(section string) bool {
	switch strings.ToLower(strings.TrimSpace(section)) {
	case "", "all", "revenue":
		return true
	default:
		return false
	}
}

func requirementsHasCohortFilter(query RequirementsQuery) bool {
	for _, value := range []string{query.UserRole, query.PayerType, query.NewUserType} {
		normalized := strings.ToLower(strings.TrimSpace(value))
		if normalized != "" && normalized != "all" {
			return true
		}
	}
	return false
}

func overlayFinanceRechargeRequirements(sections []map[string]any, overview payment.FinanceRechargeOverview) {
	byDay := make(map[string]financeRechargeValues, len(overview.Daily))
	for _, bucket := range overview.Daily {
		if bucket.Date != "" {
			byDay[bucket.Date] = financeRechargeValuesFromDaily(bucket)
		}
	}
	total := financeRechargeOverviewTotal(overview)
	for _, section := range sections {
		if rowString(section, "key") != "revenue" {
			continue
		}
		section["recharge_source"] = "finance"
		section["finance_recharge_available"] = true
		updateFinanceRequirementMetadata(section, true)
		if row, ok := section["total"].(map[string]any); ok {
			applyFinanceRequirementAmounts(row, total, 1)
		}

		daily := anyRowSlice(section["daily_series"])
		seen := map[string]struct{}{}
		for _, row := range daily {
			day := rowString(row, "stat_day", "label")
			applyFinanceRequirementAmounts(row, byDay[day], 1)
			if day != "" {
				seen[day] = struct{}{}
			}
		}
		for day, values := range byDay {
			if _, ok := seen[day]; ok {
				continue
			}
			row := map[string]any{"label": day, "stat_day": day}
			applyFinanceRequirementAmounts(row, values, 1)
			daily = append(daily, row)
		}
		sort.Slice(daily, func(i, j int) bool {
			return rowString(daily[i], "stat_day", "label") < rowString(daily[j], "stat_day", "label")
		})
		section["daily_series"] = daily
		if row, ok := section["average"].(map[string]any); ok {
			applyFinanceRequirementAmounts(row, total, len(daily))
		}
	}
}

func markFinanceRechargeRequirementsUnavailable(sections []map[string]any) {
	for _, section := range sections {
		if rowString(section, "key") != "revenue" {
			continue
		}
		section["recharge_source"] = "unavailable_for_cohort"
		section["finance_recharge_available"] = false
		updateFinanceRequirementMetadata(section, false)
		if row, ok := section["total"].(map[string]any); ok {
			markFinanceRequirementAmountsUnavailable(row)
		}
		if row, ok := section["average"].(map[string]any); ok {
			markFinanceRequirementAmountsUnavailable(row)
		}
		for _, row := range anyRowSlice(section["daily_series"]) {
			markFinanceRequirementAmountsUnavailable(row)
		}
	}
}

func applyFinanceRequirementAmounts(row map[string]any, values financeRechargeValues, dayCount int) {
	if row == nil {
		return
	}
	assign := func(key string, value int64) {
		if dayCount > 1 {
			row[key] = float64(value) / float64(dayCount)
			return
		}
		row[key] = value
	}
	assign("recharge_usd_minor", values.totalUSDMinor())
	assign("google_recharge_usd_minor", values.GoogleUSDMinor)
	assign("third_party_recharge_usd_minor", values.ThirdPartyUSDMinor)
	assign("coin_seller_recharge_usd_minor", values.CoinSellerUSDMinor)
	// Finance 无首充/复购和去重付费人数维度，这些金额均值不能继续沿用宽表分子。
	row["first_recharge_usd_minor"] = nil
	row["repeat_recharge_usd_minor"] = nil
	row["arpu_usd_minor"] = nil
	row["arppu_usd_minor"] = nil
	row["finance_recharge_aligned"] = true
}

func markFinanceRequirementAmountsUnavailable(row map[string]any) {
	if row == nil {
		return
	}
	for _, key := range []string{
		"recharge_usd_minor",
		"google_recharge_usd_minor",
		"third_party_recharge_usd_minor",
		"coin_seller_recharge_usd_minor",
		"first_recharge_usd_minor",
		"repeat_recharge_usd_minor",
		"arpu_usd_minor",
		"arppu_usd_minor",
	} {
		row[key] = nil
	}
	row["finance_recharge_aligned"] = true
}

func updateFinanceRequirementMetadata(section map[string]any, available bool) {
	definitions := map[string]string{
		"recharge_usd_minor":             "Finance 成功充值总额 = Google + 非 Google 三方 + 币商充值。",
		"google_recharge_usd_minor":      "Finance Google 成功充值。",
		"third_party_recharge_usd_minor": "Finance 非 Google 三方成功充值。",
		"coin_seller_recharge_usd_minor": "Finance 已核验币商订单 + H5 币商身份成功充值。",
		"first_recharge_usd_minor":       "Finance 暂无首充金额拆分，本字段不可用。",
		"repeat_recharge_usd_minor":      "Finance 暂无复购金额拆分，本字段不可用。",
		"arpu_usd_minor":                 "Finance 金额没有同口径活跃人数，本字段不可用。",
		"arppu_usd_minor":                "Finance 币商/H5 金额没有同口径付费人数，本字段不可用。",
	}
	if !available {
		for _, key := range []string{"recharge_usd_minor", "google_recharge_usd_minor", "third_party_recharge_usd_minor", "coin_seller_recharge_usd_minor"} {
			definitions[key] = "Finance 不支持当前角色/付费身份/新老用户筛选，本字段不可用。"
		}
	}

	metricDefinitions := anyRowSlice(section["metric_definitions"])
	definitionIndex := map[string]map[string]any{}
	for _, row := range metricDefinitions {
		definitionIndex[rowString(row, "metric", "field")] = row
	}
	for metric, definition := range definitions {
		row := definitionIndex[metric]
		if row == nil {
			row = map[string]any{"metric": metric}
			metricDefinitions = append(metricDefinitions, row)
		}
		row["definition"] = definition
	}
	section["metric_definitions"] = metricDefinitions

	columns := anyRowSlice(section["columns"])
	for _, column := range columns {
		key := rowString(column, "key")
		if definition, ok := definitions[key]; ok {
			column["tooltip"] = definition
		}
	}
	section["columns"] = columns
}

func financeRegionDayKey(regionID int64, day string) string {
	return strconv.FormatInt(regionID, 10) + "|" + day
}

func financeRechargeDelta(current int64, previous int64) float64 {
	if previous == 0 {
		if current > 0 {
			return 1
		}
		return 0
	}
	denominator := previous
	if denominator < 0 {
		denominator = -denominator
	}
	return float64(current-previous) / float64(denominator)
}

func appendFinanceRechargeMetricSources(value any) []map[string]any {
	out := anyRowSlice(value)
	fields := []struct {
		field      string
		definition string
	}{
		{"recharge_usd_minor", "Finance 总充值 = Google + 三方 + 币商充值。"},
		{"google_recharge_usd_minor", "Finance 谷歌成功充值账单。"},
		{"mifapay_recharge_usd_minor", "Finance 非 Google 三方成功充值账单。"},
		{"coin_seller_recharge_usd_minor", "Finance 已核验币商订单 + H5 币商身份成功充值。"},
	}
	for _, item := range fields {
		replaced := false
		for index := range out {
			if rowString(out[index], "field") != item.field {
				continue
			}
			out[index] = map[string]any{
				"available":  true,
				"definition": item.definition,
				"field":      item.field,
				"source":     "Finance recharge-bills overview",
			}
			replaced = true
			break
		}
		if !replaced {
			out = append(out, map[string]any{
				"available":  true,
				"definition": item.definition,
				"field":      item.field,
				"source":     "Finance recharge-bills overview",
			})
		}
	}
	return out
}
