package mysql

import (
	"context"
	"regexp"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"hyapp/pkg/appcode"
	"hyapp/services/wallet-service/internal/domain/ledger"
)

func TestGetHostRevenueStatsAggregatesOnlyHostPolicyDiamonds(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta(`
		SELECT
		  COALESCE(SUM(CASE WHEN income_type = 'SALARY_DIAMOND' THEN host_base_amount ELSE 0 END), 0),
		  COALESCE(SUM(CASE WHEN income_type = 'POINT_DIAMOND' THEN host_base_amount ELSE 0 END), 0),
		  COUNT(DISTINCT CASE WHEN sender_user_id > 0 THEN sender_user_id END)
		FROM (
		  SELECT diamond_delta AS host_base_amount, sender_user_id, 'SALARY_DIAMOND' AS income_type
		  FROM host_period_diamond_entries FORCE INDEX (idx_host_period_diamond_entries_user_cycle)
		  WHERE app_code = ? AND user_id = ? AND cycle_key BETWEEN ? AND ?
		    AND created_at_ms >= ? AND created_at_ms < ?
		  UNION ALL
		  SELECT host_base_amount, sender_user_id, 'POINT_DIAMOND' AS income_type
		  FROM point_diamond_gift_income_entries FORCE INDEX (idx_point_diamond_gift_host_time)
		  WHERE app_code = ? AND host_user_id = ?
		    AND created_at_ms >= ? AND created_at_ms < ?
		) AS gift_income`)).
		WithArgs("fami", int64(42), "1970-01", "1970-01", int64(1000), int64(2000),
			"fami", int64(42), int64(1000), int64(2000)).
		WillReturnRows(sqlmock.NewRows([]string{"salary_earnings", "point_diamond_earnings", "senders"}).AddRow(int64(8800), int64(1200), int64(7)))
	mock.ExpectQuery(regexp.QuoteMeta(`
		SELECT
		  COALESCE(SUM(CASE WHEN e.asset_type = ? THEN -e.available_delta ELSE 0 END), 0),
		  COALESCE(SUM(CASE WHEN e.asset_type = ? THEN -e.available_delta ELSE 0 END), 0)
		FROM wallet_entries e FORCE INDEX (idx_wallet_entries_asset_user_time)
		INNER JOIN wallet_transactions t
		  ON t.app_code = e.app_code AND t.transaction_id = e.transaction_id
		WHERE e.app_code = ? AND e.user_id = ? AND e.asset_type IN (?, ?)
		  AND e.created_at_ms >= ? AND e.created_at_ms < ?
		  AND e.available_delta < 0
		  AND t.biz_type IN (?, ?, ?)`)).
		WithArgs(ledger.AssetPoint, ledger.AssetPointDiamond, "fami", int64(42), ledger.AssetPoint, ledger.AssetPointDiamond, int64(1000), int64(2000),
			bizTypePointExchangeToCoin, bizTypePointTransferToCoinSeller, bizTypeSalaryWithdrawalFreeze).
		WillReturnRows(sqlmock.NewRows([]string{"legacy_exchanged", "point_diamond_exchanged"}).AddRow(int64(2400), int64(500)))
	repo := &Repository{db: db}
	stats, err := repo.GetHostRevenueStats(appcode.WithContext(context.Background(), "fami"), ledger.HostRevenueStatsQuery{
		HostUserID: 42,
		StartAtMS:  1000,
		EndAtMS:    2000,
	})
	if err != nil {
		t.Fatalf("GetHostRevenueStats failed: %v", err)
	}
	if stats.DiamondEarnings != 8800 || stats.PointDiamondEarnings != 1200 || stats.DiamondExchanged != 2400 || stats.PointDiamondExchanged != 500 || stats.GiftSenders != 7 {
		t.Fatalf("unexpected host revenue stats: %+v", stats)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet SQL expectations: %v", err)
	}
}

func TestGetAgencyHostGiftStatsUsesIndexedDiamondProjection(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery(regexp.QuoteMeta(`
		SELECT
		  host_user_id,
		  COALESCE(SUM(CASE WHEN income_type = 'SALARY_DIAMOND' THEN host_base_amount ELSE 0 END), 0),
		  COALESCE(SUM(CASE WHEN income_type = 'POINT_DIAMOND' THEN host_base_amount ELSE 0 END), 0),
		  COUNT(DISTINCT CASE WHEN sender_user_id > 0 THEN sender_user_id END),
		  MAX(CASE WHEN host_base_amount > 0 THEN 1 ELSE 0 END)
		FROM (
		  SELECT diamond_delta AS host_base_amount, user_id AS host_user_id, sender_user_id, 'SALARY_DIAMOND' AS income_type
		  FROM host_period_diamond_entries FORCE INDEX (idx_host_period_diamond_entries_user_cycle)
		  WHERE app_code = ? AND user_id IN (?,?)
		    AND cycle_key BETWEEN ? AND ?
		    AND created_at_ms >= ? AND created_at_ms < ?
		  UNION ALL
		  SELECT host_base_amount, host_user_id, sender_user_id, 'POINT_DIAMOND' AS income_type
		  FROM point_diamond_gift_income_entries FORCE INDEX (idx_point_diamond_gift_host_time)
		  WHERE app_code = ? AND host_user_id IN (?,?)
		    AND created_at_ms >= ? AND created_at_ms < ?
		) AS gift_income
		GROUP BY host_user_id`)).
		WithArgs("fami", int64(42), int64(84), "2026-06", "2026-07", int64(1780272000000), int64(1785542400000),
			"fami", int64(42), int64(84), int64(1780272000000), int64(1785542400000)).
		WillReturnRows(sqlmock.NewRows([]string{"host_user_id", "salary_earnings", "point_diamond_earnings", "senders", "gifted"}).
			AddRow(int64(42), int64(900), int64(6000000), int64(3), int64(1)).
			AddRow(int64(84), int64(0), int64(92150), int64(2), int64(1)))
	mock.ExpectQuery(regexp.QuoteMeta(`
		SELECT
		  e.user_id,
		  COALESCE(SUM(CASE WHEN e.asset_type = ? THEN -e.available_delta ELSE 0 END), 0),
		  COALESCE(SUM(CASE WHEN e.asset_type = ? THEN -e.available_delta ELSE 0 END), 0)
		FROM wallet_entries e FORCE INDEX (idx_wallet_entries_asset_user_time)
		INNER JOIN wallet_transactions t
		  ON t.app_code = e.app_code AND t.transaction_id = e.transaction_id
		WHERE e.app_code = ? AND e.user_id IN (?,?) AND e.asset_type IN (?, ?)
		  AND e.created_at_ms >= ? AND e.created_at_ms < ?
		  AND e.available_delta < 0
		  AND t.biz_type IN (?, ?, ?)
		GROUP BY e.user_id`)).
		WithArgs(ledger.AssetPoint, ledger.AssetPointDiamond, "fami", int64(42), int64(84), ledger.AssetPoint, ledger.AssetPointDiamond,
			int64(1780272000000), int64(1785542400000), bizTypePointExchangeToCoin, bizTypePointTransferToCoinSeller, bizTypeSalaryWithdrawalFreeze).
		WillReturnRows(sqlmock.NewRows([]string{"host_user_id", "point_exchanged", "point_diamond_exchanged"}).
			AddRow(int64(42), int64(200), int64(500)).
			AddRow(int64(84), int64(50), int64(70)))
	mock.ExpectQuery(regexp.QuoteMeta(`
			SELECT COALESCE(SUM(agency_amount), 0)
			FROM point_diamond_gift_income_entries FORCE INDEX (idx_point_diamond_gift_agency_time)
			WHERE app_code = ? AND agency_user_id = ?
			  AND created_at_ms >= ? AND created_at_ms < ?`)).
		WithArgs("fami", int64(42), int64(1780272000000), int64(1785542400000)).
		WillReturnRows(sqlmock.NewRows([]string{"agency_income"}).AddRow(int64(1218430)))

	repo := &Repository{db: db}
	stats, err := repo.GetAgencyHostGiftStats(appcode.WithContext(context.Background(), "fami"), ledger.AgencyHostGiftStatsQuery{
		HostUserIDs:  []int64{42, 84},
		AgencyUserID: 42,
		StartAtMS:    1780272000000,
		EndAtMS:      1785542400000,
	})
	if err != nil {
		t.Fatalf("GetAgencyHostGiftStats failed: %v", err)
	}
	if stats.GiftIncome != 900 || stats.PointDiamondHostIncome != 6092150 || stats.PointDiamondAgencyShareIncome != 1218430 || stats.GiftedHostCount != 2 {
		t.Fatalf("unexpected agency host gift stats: %+v", stats)
	}
	if len(stats.HostStats) != 2 || stats.HostStats[0].HostUserID != 42 || stats.HostStats[0].Stats.DiamondEarnings != 900 || stats.HostStats[0].Stats.PointDiamondEarnings != 6000000 || stats.HostStats[0].Stats.DiamondExchanged != 200 || stats.HostStats[0].Stats.PointDiamondExchanged != 500 || stats.HostStats[0].Stats.GiftSenders != 3 {
		t.Fatalf("unexpected first host stats: %+v", stats.HostStats)
	}
	if stats.HostStats[1].HostUserID != 84 || stats.HostStats[1].Stats.DiamondEarnings != 0 || stats.HostStats[1].Stats.PointDiamondEarnings != 92150 || stats.HostStats[1].Stats.DiamondExchanged != 50 || stats.HostStats[1].Stats.PointDiamondExchanged != 70 || stats.HostStats[1].Stats.GiftSenders != 2 {
		t.Fatalf("unexpected second host stats: %+v", stats.HostStats)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet SQL expectations: %v", err)
	}
}

func TestInsertPointDiamondGiftIncomeProjectionPreservesHostAgencySplit(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.Begin()
	if err != nil {
		t.Fatalf("begin transaction: %v", err)
	}
	mock.ExpectExec(regexp.QuoteMeta(`
		INSERT INTO point_diamond_gift_income_entries (
		  app_code, transaction_id, host_user_id, agency_user_id, sender_user_id,
		  host_base_amount, agency_amount, created_at_ms
		) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)).
		WithArgs("fami", "wtx-self-host", int64(42), int64(42), int64(9), int64(100), int64(20), int64(1785542400000)).
		WillReturnResult(sqlmock.NewResult(1, 1))

	repo := &Repository{db: db}
	err = repo.insertPointDiamondGiftIncomeProjection(
		appcode.WithContext(context.Background(), "fami"),
		tx,
		"wtx-self-host",
		giftMetadata{
			HostIncomePolicyType:    ledger.HostPolicyTypePointDiamond,
			IncomeAssetType:         ledger.AssetPointDiamond,
			TargetUserID:            42,
			TargetAgencyOwnerUserID: 42,
			SenderUserID:            9,
			TargetHostRegionID:      25,
			HostSalaryPolicyID:      81,
			HostSalaryPolicyVersion: 7,
			HostBaseAmount:          100,
			AgencyAmount:            20,
		},
		1785542400000,
	)
	if err != nil {
		t.Fatalf("insert projection: %v", err)
	}
	mock.ExpectRollback()
	if err := tx.Rollback(); err != nil {
		t.Fatalf("rollback transaction: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet SQL expectations: %v", err)
	}
}

func TestInsertPointDiamondGiftIncomeProjectionSkipsSalaryPolicy(t *testing.T) {
	// 工资型礼物仍只写 host_period_diamond_entries；即使它也携带 HostBaseAmount，
	// 这里必须在接触事务前返回，避免同一礼物被两个统计来源重复累计。
	err := (&Repository{}).insertPointDiamondGiftIncomeProjection(
		context.Background(),
		nil,
		"wtx-salary",
		giftMetadata{
			HostIncomePolicyType: ledger.HostPolicyTypeSalaryDiamond,
			IncomeAssetType:      ledger.AssetHostPeriodDiamond,
			TargetUserID:         42,
			HostBaseAmount:       100,
		},
		1785542400000,
	)
	if err != nil {
		t.Fatalf("salary policy must not write point projection: %v", err)
	}
}
