package tests

import (
	"testing"
	"time"

	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
)

type ReferralTestSuite struct {
	tests.BillingTestSuite
}

func TestReferralSuite(t *testing.T) {
	suite.Run(t, new(ReferralTestSuite))
}

func (s *ReferralTestSuite) SetupTest() {
	s.BillingTestSuite.SetupTest()
	assert := s.Assert()

	// Setup tenant 1 (referrer A)
	err := datainit.CreatePaymentMock(1)
	assert.NoError(err)
	_, err = datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)

	// Set referral config (global)
	config.Get().Set(config.ReferralRewardConfig, `{"credits_validity_days":365,"usage":"referral"}`)
	config.Get().Set(config.ReferralShareLinkBase, "https://tidbcloud.com/signup")
	config.Get().Set(config.ReferralAmountSignup, 5)
	config.Get().Set(config.ReferralAmountRefereeSignup, 5)
	config.Get().Set(config.ReferralAmountBindCard, 5)
	config.Get().Set(config.ReferralAmountRefereeBindCard, 5)
	config.Get().Set(config.CreditsPricePackage, datainit.NoUsePackageId)

	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate
	}
}

var referralModel = &marketing.ReferralModel{}

// ========== Campaign Tests ==========

func (s *ReferralTestSuite) TestCreateCampaign() {
	assert := s.Assert()
	resp, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:              "Test Campaign",
		TotalBudget:       decimal.NewFromInt(1000),
		Note:              "test note",
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)
	assert.NotZero(resp.ID)
	assert.Equal("Test Campaign", resp.Name)
	assert.Equal(entity.ReferralCampaignStatus_Active, resp.Status)
	assert.Equal(decimal.NewFromInt(1000), resp.TotalBudget)
	assert.Equal(decimal.Zero, resp.UsedBudget)
	assert.Equal(decimal.NewFromInt(1000), resp.RemainingBudget)
	assert.Equal("admin@test.com", resp.CreatedBy)
	assert.Equal("test note", resp.Note)
}

func (s *ReferralTestSuite) TestCreateCampaignInvalidBudget() {
	assert := s.Assert()
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Bad Campaign",
		TotalBudget: decimal.NewFromInt(-100),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.Error(err)
}

func (s *ReferralTestSuite) TestListCampaigns() {
	assert := s.Assert()
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Campaign 1",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)
	_, err = referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Campaign 2",
		TotalBudget: decimal.NewFromInt(2000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Len(list.Campaigns, 2)
	assert.Equal(decimal.NewFromInt(3000), list.TotalBudgetAll)
	assert.Equal(decimal.Zero, list.UsedBudgetAll)
	assert.Equal(decimal.NewFromInt(3000), list.RemainingAll)
	// DESC order
	assert.Equal("Campaign 2", list.Campaigns[0].Name)
	assert.Equal("Campaign 1", list.Campaigns[1].Name)
}

func (s *ReferralTestSuite) TestGetCampaign() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Get Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	resp, err := referralModel.GetCampaign(created.ID)
	assert.NoError(err)
	assert.Equal(created.ID, resp.ID)
	assert.Equal("Get Test", resp.Name)
}

func (s *ReferralTestSuite) TestGetCampaignNotFound() {
	assert := s.Assert()
	_, err := referralModel.GetCampaign(99999)
	assert.Error(err)
}

func (s *ReferralTestSuite) TestPauseCampaign() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Pause Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.PauseCampaign(created.ID)
	assert.NoError(err)

	resp, err := referralModel.GetCampaign(created.ID)
	assert.NoError(err)
	assert.Equal(entity.ReferralCampaignStatus_Paused, resp.Status)
}

func (s *ReferralTestSuite) TestPauseCampaignNotActive() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Pause Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.EndCampaign(created.ID)
	assert.NoError(err)

	err = referralModel.PauseCampaign(created.ID)
	assert.Error(err)
}

func (s *ReferralTestSuite) TestResumeCampaign() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Resume Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.PauseCampaign(created.ID)
	assert.NoError(err)

	err = referralModel.ResumeCampaign(created.ID)
	assert.NoError(err)

	resp, err := referralModel.GetCampaign(created.ID)
	assert.NoError(err)
	assert.Equal(entity.ReferralCampaignStatus_Active, resp.Status)
}

func (s *ReferralTestSuite) TestResumeCampaignNotPaused() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Resume Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	// Active campaign can't be resumed
	err = referralModel.ResumeCampaign(created.ID)
	assert.Error(err)
}

func (s *ReferralTestSuite) TestEndCampaign() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "End Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.EndCampaign(created.ID)
	assert.NoError(err)

	resp, err := referralModel.GetCampaign(created.ID)
	assert.NoError(err)
	assert.Equal(entity.ReferralCampaignStatus_Ended, resp.Status)
}

func (s *ReferralTestSuite) TestEndCampaignAlreadyEnded() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "End Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.EndCampaign(created.ID)
	assert.NoError(err)

	err = referralModel.EndCampaign(created.ID)
	assert.Error(err)
}

func (s *ReferralTestSuite) TestEndPausedCampaign() {
	assert := s.Assert()
	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "End Paused Test",
		TotalBudget: decimal.NewFromInt(500),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.PauseCampaign(created.ID)
	assert.NoError(err)

	err = referralModel.EndCampaign(created.ID)
	assert.NoError(err)

	resp, err := referralModel.GetCampaign(created.ID)
	assert.NoError(err)
	assert.Equal(entity.ReferralCampaignStatus_Ended, resp.Status)
}

// ========== Referral Code Tests ==========

func (s *ReferralTestSuite) TestGetOrCreateReferralCode() {
	assert := s.Assert()
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:              "Code Test",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	resp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)
	assert.Contains(resp.Code, "TIDB-1-")
	assert.Equal(0, resp.ReferralCount)
	assert.Equal(decimal.Zero, resp.TotalEarned)
	assert.Contains(resp.ShareLink, "referral_code="+resp.Code)
}

func (s *ReferralTestSuite) TestGetOrCreateReferralCodeIdempotent() {
	assert := s.Assert()
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:              "Idempotent Test",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	resp1, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)
	resp2, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)
	assert.Equal(resp1.Code, resp2.Code)
}

func (s *ReferralTestSuite) TestGetOrCreateReferralCodeDifferentTenants() {
	assert := s.Assert()
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:              "Multi Tenant Test",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	// Setup tenant 2
	datainit.CreatePaymentMock(2)
	datainit.CreateBillingConfig(2, entity.BillingConfigType_DirectCustomer)

	resp1, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)
	resp2, err := referralModel.GetOrCreateReferralCode(2, true)
	assert.NoError(err)
	assert.NotEqual(resp1.Code, resp2.Code)
}

// ========== Referral Stats Tests ==========

func (s *ReferralTestSuite) TestGetReferralStatsEmpty() {
	assert := s.Assert()
	stats, err := referralModel.GetReferralStats(1, true)
	assert.NoError(err)
	assert.Empty(stats.Events)
	assert.Equal(0, stats.TotalReferrals)
}

func (s *ReferralTestSuite) TestGetReferralStatsWithEvents() {
	assert := s.Assert()

	// Create campaign + code + trigger reward
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Stats Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	_, err = referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	stats, err := referralModel.GetReferralStats(1, true)
	assert.NoError(err)
	assert.Equal(codeResp.Code, stats.Codes[0].Code)
	assert.Equal(1, stats.TotalReferrals)
	assert.Equal(decimal.NewFromInt(5), stats.TotalEarned)
	assert.Len(stats.Events, 1)
	assert.Equal(entity.ReferralEventType_Signup, stats.Events[0].EventType)
	assert.True(stats.Events[0].Rewarded)
	assert.Equal(decimal.NewFromInt(5), stats.Events[0].RewardAmount)
	assert.Equal(uint(100), stats.Events[0].RefereeTenantID)
}

// ========== TriggerReward Tests ==========

func (s *ReferralTestSuite) TestTriggerRewardSignup() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Reward Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(resp.Rewarded)
	assert.Equal(decimal.NewFromInt(5), resp.RewardAmount)
	assert.NotEmpty(resp.CreditsCode)

	// Verify campaign budget was updated
	campaign, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), campaign.UsedBudgetAll)

	// Verify referral code stats were updated
	codeResp2, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)
	assert.Equal(1, codeResp2.ReferralCount)
	assert.Equal(decimal.NewFromInt(5), codeResp2.TotalEarned)
}

func (s *ReferralTestSuite) TestTriggerRewardInvalidCode() {
	assert := s.Assert()

	_, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    "INVALID-CODE",
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.Error(err)
}

func (s *ReferralTestSuite) TestTriggerRewardDuplicate() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Dup Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(resp.Rewarded)

	// Duplicate should be skipped
	resp2, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.False(resp2.Rewarded)
	assert.Equal("already_processed", resp2.SkipReason)

	// Budget should only be consumed once
	campaign, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), campaign.UsedBudgetAll)
}

func (s *ReferralTestSuite) TestTriggerRewardBudgetExhausted() {
	assert := s.Assert()

	// Create campaign with only $3 budget (less than $5 reward)
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Small Budget",
		TotalBudget: decimal.NewFromInt(3),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.False(resp.Rewarded)
	assert.Equal(entity.ReferralSkipReason_BudgetExhausted, resp.SkipReason)

	// Verify event was still recorded
	var count int64
	db.GetDB().Model(&entity.ReferralEvent{}).Where("referee_tenant_id = ? AND event_type = ?", 100, entity.ReferralEventType_Signup).Count(&count)
	assert.Equal(int64(1), count)
}

func (s *ReferralTestSuite) TestTriggerRewardMultiCampaign() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:              "Campaign 1",
		TotalBudget:       decimal.NewFromInt(5),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	// First reward should succeed (Campaign 1 has $5)
	resp1, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(resp1.Rewarded)

	// Second reward should fail (Campaign 1 is exhausted, code is bound to Campaign 1)
	resp2, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 200,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.False(resp2.Rewarded)

	// Verify: only Campaign 1 used
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), list.UsedBudgetAll)
}

func (s *ReferralTestSuite) TestTriggerRewardPausedCampaignSkipped() {
	assert := s.Assert()

	created, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:              "Paused Campaign",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	err = referralModel.PauseCampaign(created.ID)
	assert.NoError(err)

	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.False(resp.Rewarded)
	assert.Equal(entity.ReferralSkipReason_BudgetExhausted, resp.SkipReason)
}

func (s *ReferralTestSuite) TestTriggerRewardCustomEventType() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	// Model accepts any event type — amount is controlled by caller
	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       "custom_type",
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(resp.Rewarded)
}

func (s *ReferralTestSuite) TestTriggerRewardCaseInsensitiveCode() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Case Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	// Use lowercase version of the code
	lowerCode := codeResp.Code
	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    lowerCode,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(resp.Rewarded)
}

// ========== BindCard Reward Tests ==========

func (s *ReferralTestSuite) TestBindCardReward() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "BindCard Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	// First trigger signup reward
	_, err = referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	// Now find referral code via signup event and trigger bindcard reward
	code, err := referralModel.FindReferralCodeBySignupEvent(100)
	assert.NoError(err)
	assert.NotEmpty(code)
	_, err = referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_BindCard,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	// Verify two events exist
	stats, err := referralModel.GetReferralStats(1, true)
	assert.NoError(err)
	assert.Len(stats.Events, 2)
	assert.Equal(decimal.NewFromInt(10), stats.TotalEarned)
	assert.Equal(2, stats.TotalReferrals)
}

func (s *ReferralTestSuite) TestFindReferralCodeBySignupEventNonReferredUser() {
	assert := s.Assert()

	// User 200 was not referred, FindReferralCodeBySignupEvent should return error
	code, err := referralModel.FindReferralCodeBySignupEvent(200)
	assert.Error(err)
	assert.Empty(code)
}

func (s *ReferralTestSuite) TestBindCardRewardDuplicate() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "BindCard Dup",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	_, err = referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	code, err := referralModel.FindReferralCodeBySignupEvent(100)
	assert.NoError(err)

	// First bindcard
	_, err = referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_BindCard,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	// Second bindcard should not create another reward (already_processed)
	resp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_BindCard,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.Equal("already_processed", resp.SkipReason)

	// Only 2 events (signup + one bindcard), budget used = $10
	campaign, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), campaign.UsedBudgetAll)
}

// ========== Referee Reward Tests ==========

func (s *ReferralTestSuite) TestTriggerRefereeRewardSignup() {
	assert := s.Assert()

	// Setup referee tenant 100
	datainit.CreatePaymentMock(100)
	datainit.CreateBillingConfig(100, entity.BillingConfigType_DirectCustomer)

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Referee Test",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	// Trigger referee_signup reward — credits go to tenant 100 (B), not tenant 1 (A)
	resp, err := referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_RefereeSignup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(resp.Rewarded)
	assert.Equal(decimal.NewFromInt(5), resp.RewardAmount)
	assert.NotEmpty(resp.CreditsCode)

	// Verify campaign budget was deducted
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), list.UsedBudgetAll)

	// Verify event was recorded
	var event entity.ReferralEvent
	err = db.GetDB().Where("referee_tenant_id = ? AND event_type = ?", 100, entity.ReferralEventType_RefereeSignup).First(&event).Error
	assert.NoError(err)
	assert.True(event.Rewarded)
	assert.Equal(uint(1), event.ReferrerTenantID)
}

func (s *ReferralTestSuite) TestTriggerRefereeRewardDuplicate() {
	assert := s.Assert()

	datainit.CreatePaymentMock(100)
	datainit.CreateBillingConfig(100, entity.BillingConfigType_DirectCustomer)

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Dup Referee",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	_, err = referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_RefereeSignup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	// Duplicate should be skipped
	resp, err := referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_RefereeSignup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.False(resp.Rewarded)
	assert.Equal("already_processed", resp.SkipReason)

	// Budget should only be consumed once
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), list.UsedBudgetAll)
}

func (s *ReferralTestSuite) TestTriggerRefereeRewardBudgetExhausted() {
	assert := s.Assert()

	datainit.CreatePaymentMock(100)
	datainit.CreateBillingConfig(100, entity.BillingConfigType_DirectCustomer)

	// Small budget
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Small Referee",
		TotalBudget: decimal.NewFromInt(3),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	resp, err := referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_RefereeSignup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.False(resp.Rewarded)
	assert.Equal(entity.ReferralSkipReason_BudgetExhausted, resp.SkipReason)
}

// ========== TriggerCommunityReward Tests ==========

func (s *ReferralTestSuite) TestTriggerCommunityReward() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Community Campaign",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	err = referralModel.TriggerCommunityReward(1, decimal.NewFromFloat(3.50))
	assert.NoError(err)

	// Verify campaign budget was deducted
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.True(decimal.NewFromFloat(3.50).Equal(list.UsedBudgetAll))

	// Verify credits package was created for tenant 1
	var pkg entity.CreditsPackage
	err = db.GetDB().Where("tenant_id = ? AND usage_extend = ?", 1, entity.ReferralEventType_CommunityPayment).First(&pkg).Error
	assert.NoError(err)
	assert.True(decimal.NewFromFloat(3.50).Equal(pkg.TotalCredits))
}

func (s *ReferralTestSuite) TestTriggerCommunityRewardMultipleTimes() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Community Multi",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	// Call twice — unlike other rewards, community_payment is NOT idempotent
	err = referralModel.TriggerCommunityReward(1, decimal.NewFromFloat(2.00))
	assert.NoError(err)
	err = referralModel.TriggerCommunityReward(1, decimal.NewFromFloat(3.00))
	assert.NoError(err)

	// Both should succeed — 2 credits packages
	var count int64
	db.GetDB().Model(&entity.CreditsPackage{}).Where("tenant_id = ? AND usage_extend = ?", 1, entity.ReferralEventType_CommunityPayment).Count(&count)
	assert.Equal(int64(2), count)

	// Budget: 2 + 3 = 5
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.True(decimal.NewFromInt(5).Equal(list.UsedBudgetAll))
}

func (s *ReferralTestSuite) TestTriggerCommunityRewardBudgetExhausted() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Small Community",
		TotalBudget: decimal.NewFromInt(2),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	// Reward amount > budget — should be silently skipped (no error)
	err = referralModel.TriggerCommunityReward(1, decimal.NewFromInt(5))
	assert.NoError(err)

	// No credits package created
	var count int64
	db.GetDB().Model(&entity.CreditsPackage{}).Where("tenant_id = ? AND usage_extend = ?", 1, entity.ReferralEventType_CommunityPayment).Count(&count)
	assert.Equal(int64(0), count)

	// Budget unchanged
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.True(decimal.Zero.Equal(list.UsedBudgetAll))
}

func (s *ReferralTestSuite) TestTriggerCommunityRewardZeroAmount() {
	assert := s.Assert()

	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Zero Community",
		TotalBudget: decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	}, "admin@test.com")
	assert.NoError(err)

	// Zero amount — should be skipped
	err = referralModel.TriggerCommunityReward(1, decimal.Zero)
	assert.NoError(err)

	var count int64
	db.GetDB().Model(&entity.CreditsPackage{}).Where("tenant_id = ? AND usage_extend = ?", 1, entity.ReferralEventType_CommunityPayment).Count(&count)
	assert.Equal(int64(0), count)
}

// ========== Full Flow Test ==========

func (s *ReferralTestSuite) TestFullReferralFlow() {
	assert := s.Assert()

	// Setup referee tenant 100
	datainit.CreatePaymentMock(100)
	datainit.CreateBillingConfig(100, entity.BillingConfigType_DirectCustomer)

	// 1. Admin creates campaign
	_, err := referralModel.CreateCampaign(&forms.CreateCampaignReq{
		Name:        "Q1 2026",
		TotalBudget: decimal.NewFromInt(10000),
		IsWebsiteCampaign: true,
	}, "admin@company.com")
	assert.NoError(err)

	// 2. User A gets referral code
	codeResp, err := referralModel.GetOrCreateReferralCode(1, true)
	assert.NoError(err)
	assert.Contains(codeResp.Code, "TIDB-")
	assert.Contains(codeResp.ShareLink, "referral_code=")

	// 3. User B registers with A's code -> signup reward for referrer (A)
	signupResp, err := referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(signupResp.Rewarded)
	assert.Equal(decimal.NewFromInt(5), signupResp.RewardAmount)

	// 3b. Referee (B) also gets referee_signup reward
	refereeSignupResp, err := referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_RefereeSignup,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)
	assert.True(refereeSignupResp.Rewarded)
	assert.Equal(decimal.NewFromInt(5), refereeSignupResp.RewardAmount)

	// 4. User B binds credit card -> bindcard reward for referrer (A)
	code, err := referralModel.FindReferralCodeBySignupEvent(100)
	assert.NoError(err)
	_, err = referralModel.TriggerReward(&forms.ReferralRewardReq{
		ReferralCode:    code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_BindCard,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	// 4b. Referee (B) also gets referee_bindcard reward
	_, err = referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
		ReferralCode:    code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_RefereeBindCard,
		CreditsAmount:   decimal.NewFromInt(5),
	})
	assert.NoError(err)

	// 5. Verify A's stats (signup + bindcard = 2 events for referrer)
	stats, err := referralModel.GetReferralStats(1, true)
	assert.NoError(err)
	assert.Equal(2, stats.TotalReferrals)
	assert.Equal(decimal.NewFromInt(10), stats.TotalEarned)
	assert.Len(stats.Events, 4) // signup + referee_signup + bindcard + referee_bindcard

	// 6. Verify campaign budget (4 rewards x $5 = $20)
	list, err := referralModel.ListCampaigns()
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(20), list.UsedBudgetAll)
	assert.Equal(decimal.NewFromInt(9980), list.RemainingAll)
}
