package payment

import (
	"context"
	"testing"
	"time"

	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/modules/dashboard"

	"go.mongodb.org/mongo-driver/v2/bson"
)

func yumiBillSourceConfig() config.FinanceBillSourceConfig {
	return config.FinanceBillSourceConfig{
		Enabled:         true,
		AppCode:         "yumi",
		AppName:         "Yumi",
		LogoURL:         "https://media.haiyihy.com/admin/apps/logos/yumi.png",
		SysOrigin:       "LIKEI",
		MongoDatabase:   "test",
		MongoCollection: "in_app_purchase_details",
		RequestTimeout:  time.Second,
	}
}

func TestLegacyRechargeBillSourceExposesConfiguredLogoURL(t *testing.T) {
	source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
	if source.LogoURL() != "https://media.haiyihy.com/admin/apps/logos/yumi.png" {
		t.Fatalf("logo url=%q", source.LogoURL())
	}
}

func aslanBillSourceConfig() config.FinanceBillSourceConfig {
	cfg := yumiBillSourceConfig()
	cfg.AppCode = "aslan"
	cfg.AppName = "Aslan"
	cfg.SysOrigin = "ATYOU"
	cfg.MongoDatabase = "atyou"
	return cfg
}

func TestLegacyRechargeBillDTOMapsGooglePurchase(t *testing.T) {
	amount, err := bson.ParseDecimal128("164.99")
	if err != nil {
		t.Fatalf("parse amount: %v", err)
	}
	amountUSD, err := bson.ParseDecimal128("2.99")
	if err != nil {
		t.Fatalf("parse amount usd: %v", err)
	}
	createTime := time.UnixMilli(1_751_000_000_000).UTC()
	dto := legacyRechargeBillDTO(yumiBillSourceConfig(), legacyPurchaseDocument{
		ID:           "175100000000012345",
		OrderID:      "GPA.1234-5678-9012-34567",
		AcceptUserID: 42,
		CountryCode:  "TR",
		Currency:     "try",
		Amount:       amount,
		AmountUSD:    amountUSD,
		Status:       "SUCCESS",
		Factory:      legacyPurchaseFactory{Platform: "Android", FactoryCode: "GOOGLE"},
		Products: []legacyPurchaseProduct{
			{Name: "GOLD", Content: "560000"},
			{Name: "PROPS", Content: "AVATAR_FRAME"},
		},
		CreateTime: createTime,
	})

	if dto.AppCode != "yumi" || dto.TransactionID != "175100000000012345" {
		t.Fatalf("unexpected identity: %+v", dto)
	}
	if dto.RechargeType != "google_play_recharge" || dto.ProviderCode != "google" {
		t.Fatalf("unexpected channel mapping: %+v", dto)
	}
	if dto.Status != "succeeded" || dto.ExternalRef != "GPA.1234-5678-9012-34567" {
		t.Fatalf("unexpected status/external ref: %+v", dto)
	}
	if dto.CoinAmount != 560000 {
		t.Fatalf("coin amount = %d, want 560000", dto.CoinAmount)
	}
	if dto.USDMinorAmount != 299 {
		t.Fatalf("usd minor = %d, want 299", dto.USDMinorAmount)
	}
	if dto.UserPaidCurrencyCode != "TRY" || dto.UserPaidAmountMicro != 164_990_000 {
		t.Fatalf("unexpected user paid: %+v", dto)
	}
	// 谷歌账单初始必须是未同步（0），等 Orders API 实付缓存回填后才算同步完成。
	if dto.CreatedAtMS != createTime.UnixMilli() || dto.PaidSyncedAtMS != 0 {
		t.Fatalf("unexpected timestamps: %+v", dto)
	}

	mifapayDTO := legacyRechargeBillDTO(yumiBillSourceConfig(), legacyPurchaseDocument{
		ID:         "175100000000054321",
		Factory:    legacyPurchaseFactory{Platform: "H5", FactoryCode: "MIFA_PAY"},
		CreateTime: createTime,
	})
	if mifapayDTO.RechargeType != "mifapay" || mifapayDTO.PaidSyncedAtMS != mifapayDTO.CreatedAtMS {
		t.Fatalf("unexpected mifapay mapping: %+v", mifapayDTO)
	}
}

func TestLegacyRechargeTypeMapping(t *testing.T) {
	cases := map[string]string{
		"GOOGLE":    "google_play_recharge",
		"APPLE":     "apple_recharge",
		"HUAWEI":    "huawei_recharge",
		"TELEGRAM":  "telegram_recharge",
		"PAYER_MAX": "web_recharge",
		"":          "web_recharge",
	}
	for factoryCode, want := range cases {
		if got := legacyRechargeType(factoryCode); got != want {
			t.Fatalf("legacyRechargeType(%q) = %q, want %q", factoryCode, got, want)
		}
	}
}

func TestLegacyDecimalScaledHandlesTypes(t *testing.T) {
	decimal, err := bson.ParseDecimal128("6.99")
	if err != nil {
		t.Fatalf("parse decimal: %v", err)
	}
	if got := legacyDecimalToMinor(decimal); got != 699 {
		t.Fatalf("decimal minor = %d, want 699", got)
	}
	if got := legacyDecimalToMicro("54.99"); got != 54_990_000 {
		t.Fatalf("string micro = %d, want 54990000", got)
	}
	if got := legacyDecimalToMinor(int64(3)); got != 300 {
		t.Fatalf("int64 minor = %d, want 300", got)
	}
	if got := legacyDecimalToMinor(nil); got != 0 {
		t.Fatalf("nil minor = %d, want 0", got)
	}
	if got := legacyDecimalToMinor("not-a-number"); got != 0 {
		t.Fatalf("invalid minor = %d, want 0", got)
	}
}

type staticLegacyRegionResolver struct {
	codes   []string
	handled bool
}

func (r staticLegacyRegionResolver) CountryCodesForRegion(context.Context, string, int64) ([]string, bool, error) {
	return r.codes, r.handled, nil
}

func TestLegacyBillFilterBuildsQuery(t *testing.T) {
	source := &MongoRechargeBillSource{
		config:          yumiBillSourceConfig(),
		regionResolvers: []legacyRegionCountryResolver{staticLegacyRegionResolver{codes: []string{"tr", "SA"}, handled: true}},
	}
	filter, regionCodes, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{
		Keyword:   "GPA.1",
		RegionID:  9_000_000_000_000_000_001,
		StartAtMS: 1_000,
		EndAtMS:   2_000,
	})
	if err != nil || !matchable {
		t.Fatalf("billFilter err=%v matchable=%v", err, matchable)
	}
	if filter["sysOrigin"] != "LIKEI" || filter["status"] != "SUCCESS" {
		t.Fatalf("unexpected base filter: %v", filter)
	}
	timeRange, ok := filter["createTime"].(bson.M)
	if !ok || !timeRange["$gte"].(time.Time).Equal(time.UnixMilli(1_000)) || !timeRange["$lt"].(time.Time).Equal(time.UnixMilli(2_000)) {
		t.Fatalf("unexpected time range: %v", filter["createTime"])
	}
	// likei 账单不带国家码；区域条件必须以国家码清单返回，由聚合阶段按付款用户资料匹配。
	if len(regionCodes) != 2 || regionCodes[0] != "TR" || regionCodes[1] != "SA" {
		t.Fatalf("unexpected region codes: %v", regionCodes)
	}
	if _, ok := filter["countryCode"]; ok {
		t.Fatalf("bill countryCode filter should not be used: %v", filter)
	}
	if _, ok := filter["$or"]; !ok {
		t.Fatalf("keyword filter missing: %v", filter)
	}
}

func TestLegacyBillFilterExcludesFreightGoldRecharge(t *testing.T) {
	source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
	filter, _, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{RechargeType: "third_party"})
	if err != nil || !matchable {
		t.Fatalf("billFilter err=%v matchable=%v", err, matchable)
	}
	nor, ok := filter["$nor"].(bson.A)
	if !ok || len(nor) != len(legacyFreightCommodityFields) {
		t.Fatalf("freight exclusion missing: %v", filter)
	}
	for _, condition := range nor {
		item, ok := condition.(bson.M)
		if !ok {
			t.Fatalf("unexpected freight condition: %#v", condition)
		}
		for _, value := range item {
			typed, ok := value.(bson.M)
			if !ok || typed["$in"] == nil {
				t.Fatalf("unexpected freight condition value: %#v", value)
			}
		}
	}
}

func TestLegacyBillFilterUnresolvedRegionMatchesNothing(t *testing.T) {
	source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
	_, _, matchable, err := source.billFilter(context.Background(), legacyRechargeBillQuery{RegionID: 123})
	if err != nil {
		t.Fatalf("billFilter err=%v", err)
	}
	if matchable {
		t.Fatalf("unresolved region should not be matchable")
	}
}

type fakeDashboardRechargeSource struct {
	appCode   string
	overview  map[string]any
	query     dashboard.StatisticsQuery
	calls     int
	bills     []dashboard.CoinSellerRechargeBill
	billTotal int64
	billQuery dashboard.CoinSellerRechargeBillQuery
	billCalls int
}

func (s *fakeDashboardRechargeSource) AppCode() string {
	return s.appCode
}

func (s *fakeDashboardRechargeSource) StatisticsOverview(_ context.Context, query dashboard.StatisticsQuery) (map[string]any, error) {
	s.query = query
	s.calls++
	return s.overview, nil
}

func (s *fakeDashboardRechargeSource) ListCoinSellerRechargeBills(_ context.Context, query dashboard.CoinSellerRechargeBillQuery) ([]dashboard.CoinSellerRechargeBill, int64, error) {
	s.billQuery = query
	s.billCalls++
	return s.bills, s.billTotal, nil
}

func TestLegacyCoinSellerListDoesNotUseDashboardBillSource(t *testing.T) {
	dashboardSource := &fakeDashboardRechargeSource{
		appCode:   "aslan",
		billTotal: 2,
		bills: []dashboard.CoinSellerRechargeBill{
			{
				TransactionID:  "aslan-freight-purchase-99",
				Source:         "freight_purchase",
				UserID:         2069828597070528514,
				CountryCode:    "PH",
				USDMinorAmount: 132790,
				CreatedAtMS:    1_783_530_000_000,
			},
			{
				TransactionID:  "aslan-freight-deduction-100",
				Source:         "freight_deduction",
				UserID:         2069828597070528515,
				CountryCode:    "AE",
				USDMinorAmount: -100000,
				CreatedAtMS:    1_783_530_060_000,
			},
		},
	}
	source := &MongoRechargeBillSource{
		config: aslanBillSourceConfig(),
		regionSources: []MoneyRegionSource{&staticMoneyRegionSource{regions: []moneyRegionDTO{{
			AppCode:   "aslan",
			RegionID:  301,
			Name:      "Philippines",
			Countries: []string{"PH"},
		}, {
			AppCode:   "aslan",
			RegionID:  302,
			Name:      "United Arab Emirates",
			Countries: []string{"AE"},
		}}}},
	}
	source.SetDashboardCoinSellerSource(dashboardSource)

	items, total, err := source.ListRechargeBills(context.Background(), legacyRechargeBillQuery{
		RechargeType: "coin_seller",
		StartAtMS:    1_783_468_800_000,
		EndAtMS:      1_786_060_800_000,
		Page:         1,
		PageSize:     20,
	})
	if err != nil {
		t.Fatalf("ListRechargeBills: %v", err)
	}
	if total != 0 || len(items) != 0 {
		t.Fatalf("legacy dashboard coin seller bills should be disabled: total=%d items=%+v", total, items)
	}
	if dashboardSource.billCalls != 0 {
		t.Fatalf("dashboard bill source should not be called: calls=%d query=%+v", dashboardSource.billCalls, dashboardSource.billQuery)
	}
}

func TestLegacyCoinSellerDashboardStatsMergeSummary(t *testing.T) {
	dashboardSource := &fakeDashboardRechargeSource{
		appCode: "Yumi",
		overview: map[string]any{
			"coin_seller_recharge_usd_minor": int64(350_010),
		},
	}
	source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
	source.SetDashboardCoinSellerSource(dashboardSource)

	shanghai, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		t.Fatalf("load location: %v", err)
	}
	start := time.Date(2026, 7, 5, 0, 0, 0, 0, shanghai)
	summary := rechargeBillSummaryDTO{
		GooglePlay: rechargeBillSummaryBucketDTO{BillCount: 2, USDMinorAmount: 10_000},
		ThirdParty: rechargeBillSummaryBucketDTO{BillCount: 3, USDMinorAmount: 20_000},
	}
	if err := source.mergeCoinSellerSummary(context.Background(), legacyRechargeBillQuery{
		StartAtMS: start.UnixMilli(),
		EndAtMS:   start.AddDate(0, 0, 1).UnixMilli(),
	}, 480, &summary); err != nil {
		t.Fatalf("mergeCoinSellerSummary: %v", err)
	}
	legacyRecomputeSummaryTotal(&summary)

	if summary.CoinSeller.USDMinorAmount != 350_010 {
		t.Fatalf("coin seller usd = %d, want 350010", summary.CoinSeller.USDMinorAmount)
	}
	if summary.Total.USDMinorAmount != 380_010 || summary.Total.BillCount != 5 {
		t.Fatalf("unexpected total after coin seller merge: %+v", summary.Total)
	}
	if dashboardSource.calls != 1 || dashboardSource.query.StatTZ != "Asia/Shanghai" {
		t.Fatalf("dashboard query mismatch: calls=%d query=%+v", dashboardSource.calls, dashboardSource.query)
	}
}

func TestLegacyCoinSellerDashboardStatsRejectsPartialDayRange(t *testing.T) {
	dashboardSource := &fakeDashboardRechargeSource{
		appCode:  "yumi",
		overview: map[string]any{"coin_seller_recharge_usd_minor": int64(100)},
	}
	source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
	source.SetDashboardCoinSellerSource(dashboardSource)

	shanghai, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		t.Fatalf("load location: %v", err)
	}
	start := time.Date(2026, 7, 5, 10, 0, 0, 0, shanghai)
	summary := rechargeBillSummaryDTO{}
	err = source.mergeCoinSellerSummary(context.Background(), legacyRechargeBillQuery{
		StartAtMS: start.UnixMilli(),
		EndAtMS:   start.Add(2 * time.Hour).UnixMilli(),
	}, 480, &summary)
	if err == nil {
		t.Fatalf("partial day range should fail")
	}
	if dashboardSource.calls != 0 {
		t.Fatalf("dashboard source should not be called for partial day range")
	}

	err = source.mergeCoinSellerSummary(context.Background(), legacyRechargeBillQuery{}, 480, &summary)
	if err == nil {
		t.Fatalf("missing day range should fail")
	}
}

func TestLegacyCoinSellerDashboardStatsRequiresMetricField(t *testing.T) {
	dashboardSource := &fakeDashboardRechargeSource{
		appCode:  "yumi",
		overview: map[string]any{"coin_seller_recharge_usd_minor": nil},
	}
	source := &MongoRechargeBillSource{config: yumiBillSourceConfig()}
	source.SetDashboardCoinSellerSource(dashboardSource)

	shanghai, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		t.Fatalf("load location: %v", err)
	}
	start := time.Date(2026, 7, 5, 0, 0, 0, 0, shanghai)
	summary := rechargeBillSummaryDTO{}
	err = source.mergeCoinSellerSummary(context.Background(), legacyRechargeBillQuery{
		StartAtMS: start.UnixMilli(),
		EndAtMS:   start.AddDate(0, 0, 1).UnixMilli(),
	}, 480, &summary)
	if err == nil {
		t.Fatalf("missing dashboard coin seller field should fail")
	}
	if dashboardSource.calls != 1 {
		t.Fatalf("dashboard source calls = %d, want 1", dashboardSource.calls)
	}
}

func TestLegacyCoinSellerDashboardStatsMergeOverview(t *testing.T) {
	dashboardSource := &fakeDashboardRechargeSource{
		appCode: "yumi",
		overview: map[string]any{
			"coin_seller_recharge_usd_minor": int64(5_000),
			"daily_series": []map[string]any{
				{"stat_day": "2026-07-05", "coin_seller_recharge_usd_minor": int64(4_200)},
				{"stat_day": "2026-07-06", "coin_seller_recharge_usd_minor": int64(800)},
			},
			"country_breakdown": []map[string]any{
				{"country_code": "SA", "coin_seller_recharge_usd_minor": int64(4_200)},
				{"country_code": "AE", "coin_seller_recharge_usd_minor": int64(800)},
			},
		},
	}
	source := &MongoRechargeBillSource{
		config: yumiBillSourceConfig(),
		regionSources: []MoneyRegionSource{&staticMoneyRegionSource{regions: []moneyRegionDTO{{
			AppCode:   "yumi",
			RegionID:  101,
			Name:      "Gulf",
			Countries: []string{"SA", "AE"},
		}}}},
	}
	source.SetDashboardCoinSellerSource(dashboardSource)

	shanghai, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		t.Fatalf("load location: %v", err)
	}
	start := time.Date(2026, 7, 5, 0, 0, 0, 0, shanghai)
	overview := rechargeBillOverviewDTO{
		Daily: []rechargeBillDailyBucketDTO{{Date: "2026-07-05", ThirdPartyUsdMinor: 2_000}},
		Regions: []rechargeBillRegionBucketDTO{{
			RegionID:       101,
			Name:           "Gulf",
			UsdMinorAmount: 2_000,
		}},
	}
	if err := source.mergeCoinSellerOverview(context.Background(), legacyRechargeBillQuery{
		StartAtMS: start.UnixMilli(),
		EndAtMS:   start.AddDate(0, 0, 2).UnixMilli(),
	}, 480, &overview); err != nil {
		t.Fatalf("mergeCoinSellerOverview: %v", err)
	}
	if len(overview.Daily) != 2 {
		t.Fatalf("daily buckets = %d, want 2: %+v", len(overview.Daily), overview.Daily)
	}
	if overview.Daily[0].Date != "2026-07-05" || overview.Daily[0].CoinSellerUsdMinor != 4_200 {
		t.Fatalf("existing day not merged: %+v", overview.Daily)
	}
	if overview.Daily[1].Date != "2026-07-06" || overview.Daily[1].CoinSellerUsdMinor != 800 {
		t.Fatalf("new day not appended: %+v", overview.Daily)
	}
	if len(overview.Regions) != 1 || overview.Regions[0].UsdMinorAmount != 7_000 {
		t.Fatalf("region not merged: %+v", overview.Regions)
	}
}

func TestLegacyRegionLookupStages(t *testing.T) {
	if stages := legacyRegionLookupStages(nil); len(stages) != 0 {
		t.Fatalf("empty codes should not add stages: %v", stages)
	}
	stages := legacyRegionLookupStages([]string{"TR", "SA"})
	if len(stages) != 3 {
		t.Fatalf("expected lookup/addFields/match stages, got %d", len(stages))
	}
	lookup, ok := stages[0][0].Value.(bson.M)
	if !ok || lookup["from"] != legacyUserProfileCollection || lookup["localField"] != "acceptUserId" || lookup["foreignField"] != "_id" {
		t.Fatalf("unexpected lookup stage: %v", stages[0])
	}
	match, ok := stages[2][0].Value.(bson.M)
	if !ok {
		t.Fatalf("unexpected match stage: %v", stages[2])
	}
	condition, ok := match["legacyBillCountry"].(bson.M)
	if !ok {
		t.Fatalf("match stage missing country condition: %v", match)
	}
	codes, ok := condition["$in"].([]string)
	if !ok || len(codes) != 2 || codes[0] != "TR" || codes[1] != "SA" {
		t.Fatalf("unexpected region codes in match: %v", condition["$in"])
	}
}
