package databi

import (
	"context"
	"testing"

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

type fakeFinanceRechargeOverviewSource struct {
	calls []payment.FinanceRechargeOverviewQuery
}

func (f *fakeFinanceRechargeOverviewSource) FinanceRechargeOverview(_ context.Context, query payment.FinanceRechargeOverviewQuery) (payment.FinanceRechargeOverview, error) {
	f.calls = append(f.calls, query)
	if query.EndAtMS == 200 {
		return payment.FinanceRechargeOverview{Daily: []payment.FinanceRechargeDailyBucket{{
			Date: "1970-01-01", GoogleUSDMinor: 50, ThirdPartyUSDMinor: 100, CoinSellerUSDMinor: 50,
		}}}, nil
	}
	region := payment.FinanceRechargeRegionOverview{RegionID: 7, TotalUSDMinor: 150}
	if len(query.ScopeRegionIDs) > 0 {
		region.DailyAvailable = true
		region.Daily = []payment.FinanceRechargeDailyBucket{{
			Date: "2026-07-10", GoogleUSDMinor: 40, ThirdPartyUSDMinor: 50, CoinSellerUSDMinor: 60,
		}}
	}
	return payment.FinanceRechargeOverview{
		Daily: []payment.FinanceRechargeDailyBucket{
			{Date: "2026-07-10", GoogleUSDMinor: 100, ThirdPartyUSDMinor: 200, CoinSellerUSDMinor: 300},
			{Date: "2026-07-11", GoogleUSDMinor: 10, ThirdPartyUSDMinor: 20, CoinSellerUSDMinor: 30},
		},
		Regions: []payment.FinanceRechargeRegionOverview{region},
	}, nil
}

func TestApplyFinanceRechargeOverviewReplacesDashboardRechargeMetrics(t *testing.T) {
	finance := &fakeFinanceRechargeOverviewSource{}
	service := &Service{financeRecharge: finance}
	out := AppOverview{
		AppCode: "aslan",
		Total: map[string]any{
			"active_users": int64(10), "paid_users": int64(2), "recharge_usd_minor": int64(9999),
			"coin_seller_recharge_usd_minor": int64(8888), "arpu_delta_rate": 0.9, "arppu_delta_rate": 0.8,
		},
		DailySeries: []map[string]any{{
			"stat_day": "2026-07-10", "active_users": int64(5), "paid_users": int64(1),
			"recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888),
		}},
		RegionBreakdown: []map[string]any{{
			"region_id": int64(7), "active_users": int64(5), "paid_users": int64(1),
			"recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888),
		}, {
			"region_id": int64(8), "active_users": int64(3), "paid_users": int64(1),
			"recharge_usd_minor": int64(7777), "coin_seller_recharge_usd_minor": int64(6666),
		}},
		DailyRegionBreakdown: []map[string]any{{
			"region_id": int64(7), "stat_day": "2026-07-10", "active_users": int64(5), "paid_users": int64(1),
			"recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888),
		}, {
			"region_id": int64(8), "stat_day": "2026-07-10", "active_users": int64(3), "paid_users": int64(1),
			"recharge_usd_minor": int64(7777), "coin_seller_recharge_usd_minor": int64(6666),
		}},
		CountryBreakdown: []map[string]any{{
			"country_code": "SA", "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888),
		}},
		DailyCountryBreakdown: []map[string]any{{
			"country_code": "SA", "stat_day": "2026-07-10", "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888),
		}},
	}
	query := OverviewQuery{StartMS: 200, EndMS: 300}
	regions := []RegionInfo{
		{AppCode: "aslan", RegionID: 7, RegionCode: "SA", RegionName: "沙特"},
		{AppCode: "aslan", RegionID: 8, RegionCode: "AE", RegionName: "阿联酋"},
	}

	if err := service.applyFinanceRechargeOverview(context.Background(), &out, query, regions, false, nil); err != nil {
		t.Fatalf("apply Finance recharge overview failed: %v", err)
	}

	if got := mustMetric(t, out.Total, "recharge_usd_minor"); got != 660 {
		t.Fatalf("total recharge should use Finance channels: got %d", got)
	}
	if got := mustMetric(t, out.Total, "coin_seller_recharge_usd_minor"); got != 330 {
		t.Fatalf("coin seller recharge should use Finance verified/H5 amount: got %d", got)
	}
	if got := mustMetric(t, out.Total, "mifapay_recharge_usd_minor"); got != 220 {
		t.Fatalf("third-party recharge should use Finance amount: got %d", got)
	}
	if delta, ok := out.Total["recharge_delta_rate"].(float64); !ok || delta != 2.3 {
		t.Fatalf("recharge delta should compare the previous Finance range: %#v", out.Total["recharge_delta_rate"])
	}
	if out.Total["arpu_delta_rate"] != nil || out.Total["arppu_delta_rate"] != nil {
		t.Fatalf("amount-derived deltas must not retain the old Dashboard numerator: %#v", out.Total)
	}
	if out.Total["arpu_usd_minor"] != nil || out.Total["arppu_usd_minor"] != nil || out.Total["finance_recharge_aligned"] != true {
		t.Fatalf("Finance recharge must not be divided by old Dashboard users: %#v", out.Total)
	}
	if len(out.DailySeries) != 2 || mustMetric(t, out.DailySeries[0], "recharge_usd_minor") != 600 || mustMetric(t, out.DailySeries[1], "recharge_usd_minor") != 60 {
		t.Fatalf("daily series should be replaced and missing Finance days appended: %#v", out.DailySeries)
	}
	if mustMetric(t, out.RegionBreakdown[0], "recharge_usd_minor") != 150 || out.RegionBreakdown[0]["coin_seller_recharge_usd_minor"] != nil {
		t.Fatalf("global region rows should use Finance totals without inventing channel splits: %#v", out.RegionBreakdown)
	}
	if mustMetric(t, out.RegionBreakdown[1], "recharge_usd_minor") != 0 || out.DailyRegionBreakdown[0]["recharge_usd_minor"] != nil || out.DailyRegionBreakdown[1]["recharge_usd_minor"] != nil {
		t.Fatalf("regions/days missing from Finance must not retain Dashboard recharge: region=%#v daily=%#v", out.RegionBreakdown, out.DailyRegionBreakdown)
	}
	if out.CountryBreakdown[0]["recharge_usd_minor"] != nil || out.CountryBreakdown[0]["coin_seller_recharge_usd_minor"] != nil || out.DailyCountryBreakdown[0]["recharge_usd_minor"] != nil {
		t.Fatalf("country recharge must be unavailable because Finance has no country dimension: %#v %#v", out.CountryBreakdown, out.DailyCountryBreakdown)
	}
	if len(finance.calls) != 2 || !finance.calls[0].IncludeRegions || finance.calls[1].IncludeRegions || finance.calls[0].TZOffsetMinutes != 480 {
		t.Fatalf("current/previous Finance queries mismatch: %#v", finance.calls)
	}
}

func TestApplyFinanceRechargeOverviewLoadsDailyChannelsOnlyForExplicitRegions(t *testing.T) {
	finance := &fakeFinanceRechargeOverviewSource{}
	service := &Service{financeRecharge: finance}
	out := AppOverview{
		AppCode:              "aslan",
		Total:                map[string]any{},
		RegionBreakdown:      []map[string]any{{"region_id": int64(7)}},
		DailyRegionBreakdown: []map[string]any{{"region_id": int64(7), "stat_day": "2026-07-10"}},
	}
	query := OverviewQuery{StartMS: 200, EndMS: 300, RegionIDs: []int64{7}}
	regions := []RegionInfo{{AppCode: "aslan", RegionID: 7, RegionCode: "SA", RegionName: "沙特"}}

	if err := service.applyFinanceRechargeOverview(context.Background(), &out, query, regions, false, nil); err != nil {
		t.Fatalf("apply scoped Finance recharge overview failed: %v", err)
	}
	if mustMetric(t, out.RegionBreakdown[0], "recharge_usd_minor") != 150 || mustMetric(t, out.DailyRegionBreakdown[0], "coin_seller_recharge_usd_minor") != 60 {
		t.Fatalf("explicit regions should receive Finance daily channels: region=%#v daily=%#v", out.RegionBreakdown, out.DailyRegionBreakdown)
	}
	if len(finance.calls) != 2 || len(finance.calls[0].ScopeRegionIDs) != 1 || finance.calls[0].ScopeRegionIDs[0] != 7 || finance.calls[1].ScopeRegionIDs[0] != 7 {
		t.Fatalf("current and previous windows must keep the explicit region scope: %#v", finance.calls)
	}
}

func TestApplyFinanceRechargeKpiWindowUsesFinanceTotalsAndRegions(t *testing.T) {
	finance := &fakeFinanceRechargeOverviewSource{}
	service := &Service{financeRecharge: finance}
	total := map[string]any{"recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888)}
	regions := map[int64]map[string]any{7: {
		"region_id": int64(7), "recharge_usd_minor": int64(9999), "coin_seller_recharge_usd_minor": int64(8888),
	}}

	if err := service.applyFinanceRechargeKpiWindow(context.Background(), "aslan", 200, 300, total, regions); err != nil {
		t.Fatalf("apply Finance KPI window failed: %v", err)
	}
	if mustMetric(t, total, "recharge_usd_minor") != 660 || mustMetric(t, regions[7], "recharge_usd_minor") != 150 {
		t.Fatalf("KPI window should use Finance total/region values: total=%#v regions=%#v", total, regions)
	}
	if len(finance.calls) != 1 || !finance.calls[0].IncludeRegions {
		t.Fatalf("KPI window should issue one Finance query with regions: %#v", finance.calls)
	}
}

func TestOverlayFinanceRechargeRequirementsUsesFinanceAmounts(t *testing.T) {
	sections := []map[string]any{{
		"key":     "revenue",
		"total":   map[string]any{"recharge_usd_minor": int64(9999), "recharge_users": int64(4), "arppu_usd_minor": int64(123)},
		"average": map[string]any{"recharge_usd_minor": float64(9999)},
		"daily_series": []any{
			map[string]any{"stat_day": "2026-07-10", "recharge_usd_minor": int64(9999), "recharge_users": int64(3)},
			map[string]any{"stat_day": "2026-07-11", "recharge_usd_minor": int64(9999), "recharge_users": int64(1)},
		},
		"columns":            []any{map[string]any{"key": "coin_seller_recharge_usd_minor"}},
		"metric_definitions": []any{map[string]any{"metric": "coin_seller_recharge_usd_minor", "definition": "old"}},
	}}
	overlayFinanceRechargeRequirements(sections, payment.FinanceRechargeOverview{Daily: []payment.FinanceRechargeDailyBucket{
		{Date: "2026-07-10", GoogleUSDMinor: 100, ThirdPartyUSDMinor: 200, CoinSellerUSDMinor: 300},
		{Date: "2026-07-11", GoogleUSDMinor: 10, ThirdPartyUSDMinor: 20, CoinSellerUSDMinor: 30},
	}})

	total := sections[0]["total"].(map[string]any)
	daily := anyRowSlice(sections[0]["daily_series"])
	average := sections[0]["average"].(map[string]any)
	if mustMetric(t, total, "recharge_usd_minor") != 660 || mustMetric(t, total, "coin_seller_recharge_usd_minor") != 330 {
		t.Fatalf("requirements total should use Finance channels: %#v", total)
	}
	if mustMetric(t, daily[0], "recharge_usd_minor") != 600 || mustMetric(t, daily[1], "recharge_usd_minor") != 60 {
		t.Fatalf("requirements daily should use Finance channels: %#v", daily)
	}
	if average["recharge_usd_minor"] != float64(330) || total["recharge_users"] != int64(4) || total["arppu_usd_minor"] != nil {
		t.Fatalf("requirements average/non-amount fields mismatch: total=%#v average=%#v", total, average)
	}
	if sections[0]["recharge_source"] != "finance" || sections[0]["finance_recharge_available"] != true {
		t.Fatalf("requirements must expose Finance source metadata: %#v", sections[0])
	}
}

func TestFinanceRechargeRequirementsCohortAmountsAreUnavailable(t *testing.T) {
	query := RequirementsQuery{UserRole: "host", PayerType: "all", NewUserType: "all"}
	if !requirementsHasCohortFilter(query) || !requirementsIncludesRevenue("revenue") {
		t.Fatalf("cohort/revenue detection mismatch")
	}
	sections := []map[string]any{{
		"key":          "revenue",
		"total":        map[string]any{"recharge_usd_minor": int64(9999), "recharge_users": int64(2)},
		"average":      map[string]any{"recharge_usd_minor": float64(9999)},
		"daily_series": []any{map[string]any{"stat_day": "2026-07-10", "recharge_usd_minor": int64(9999)}},
	}}
	markFinanceRechargeRequirementsUnavailable(sections)
	total := sections[0]["total"].(map[string]any)
	if total["recharge_usd_minor"] != nil || total["recharge_users"] != int64(2) || sections[0]["finance_recharge_available"] != false {
		t.Fatalf("cohort should retain counts but hide non-Finance amounts: %#v", sections[0])
	}
}

func mustMetric(t *testing.T, row map[string]any, key string) int64 {
	t.Helper()
	value, ok := metricInt64(row[key])
	if !ok {
		t.Fatalf("metric %s missing from %#v", key, row)
	}
	return value
}
