package tests

import (
	"encoding/json"
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/dbaas/billing2/api"
	"github.com/pingcap/dbaas/billing2/config"
	"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/ginutils"
	"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 ReferralAPITestSuite struct {
	tests.BillingTestSuite
	innerRouter *gin.Engine
	userRouter  *gin.Engine
}

func TestReferralAPISuite(t *testing.T) {
	suite.Run(t, new(ReferralAPITestSuite))
}

func (suite *ReferralAPITestSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	assert := suite.Assert()

	err := datainit.CreatePaymentMock(1)
	assert.NoError(err)
	_, err = datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)

	config.Get().Set(config.ReferralRewardConfig, `{"credits_validity_days":365,"usage":"referral"}`)
	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.ReferralShareLinkBase, "https://tidbcloud.com/signup")
	config.Get().Set(config.CreditsPricePackage, datainit.NoUsePackageId)

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

	suite.innerRouter = gin.New()
	suite.innerRouter.Use(ginutils.ErrorMiddleWare)
	api.InitializeReferralInnerRouter(ginutils.MyRoute{Route: suite.innerRouter})

	suite.userRouter = gin.New()
	suite.userRouter.Use(ginutils.ErrorMiddleWare)
	api.InitializeReferralUserRouter(ginutils.MyRoute{Route: suite.userRouter})
}

// ========== Inner API: Campaign CRUD ==========

func (suite *ReferralAPITestSuite) TestCreateCampaignAPI() {
	assert := suite.Assert()
	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "API Campaign",
		TotalBudget: decimal.NewFromInt(5000),
		Note:        "test",
	})
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.CampaignResponse
	err := json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.NotZero(resp.ID)
	assert.Equal("API Campaign", resp.Name)
	assert.Equal(entity.ReferralCampaignStatus_Active, resp.Status)
	assert.Equal(decimal.NewFromInt(5000), resp.TotalBudget)
}

func (suite *ReferralAPITestSuite) TestCreateCampaignAPIInvalid() {
	assert := suite.Assert()
	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", map[string]interface{}{
		"total_budget": 100,
	})
	// Missing required "name" field
	assert.Equal(http.StatusBadRequest, w.Code)
}

func (suite *ReferralAPITestSuite) TestListCampaignsAPI() {
	assert := suite.Assert()

	// Create two campaigns
	tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "C1",
		TotalBudget: decimal.NewFromInt(1000),
	})
	tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "C2",
		TotalBudget: decimal.NewFromInt(2000),
	})

	w := tests.PerformGetRequest(suite.innerRouter, "/referral/campaigns")
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.CampaignListResponse
	err := json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Len(resp.Campaigns, 2)
	assert.Equal(decimal.NewFromInt(3000), resp.TotalBudgetAll)
}

func (suite *ReferralAPITestSuite) TestGetCampaignAPI() {
	assert := suite.Assert()

	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "Get Test",
		TotalBudget: decimal.NewFromInt(500),
	})
	var created forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&created)

	w = tests.PerformGetRequest(suite.innerRouter, fmt.Sprintf("/referral/campaigns/%d", created.ID))
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&resp)
	assert.Equal(created.ID, resp.ID)
	assert.Equal("Get Test", resp.Name)
}

func (suite *ReferralAPITestSuite) TestPauseCampaignAPI() {
	assert := suite.Assert()

	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "Pause Test",
		TotalBudget: decimal.NewFromInt(500),
	})
	var created forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&created)

	w = tests.PerformRequest(suite.innerRouter, "POST", fmt.Sprintf("/referral/campaigns/%d/actions/pause", created.ID), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformGetRequest(suite.innerRouter, fmt.Sprintf("/referral/campaigns/%d", created.ID))
	var resp forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&resp)
	assert.Equal(entity.ReferralCampaignStatus_Paused, resp.Status)
}

func (suite *ReferralAPITestSuite) TestResumeCampaignAPI() {
	assert := suite.Assert()

	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "Resume Test",
		TotalBudget: decimal.NewFromInt(500),
	})
	var created forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&created)

	tests.PerformRequest(suite.innerRouter, "POST", fmt.Sprintf("/referral/campaigns/%d/actions/pause", created.ID), nil)
	w = tests.PerformRequest(suite.innerRouter, "POST", fmt.Sprintf("/referral/campaigns/%d/actions/resume", created.ID), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformGetRequest(suite.innerRouter, fmt.Sprintf("/referral/campaigns/%d", created.ID))
	var resp forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&resp)
	assert.Equal(entity.ReferralCampaignStatus_Active, resp.Status)
}

func (suite *ReferralAPITestSuite) TestEndCampaignAPI() {
	assert := suite.Assert()

	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:        "End Test",
		TotalBudget: decimal.NewFromInt(500),
	})
	var created forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&created)

	w = tests.PerformRequest(suite.innerRouter, "POST", fmt.Sprintf("/referral/campaigns/%d/actions/end", created.ID), nil)
	assert.Equal(http.StatusOK, w.Code)

	w = tests.PerformGetRequest(suite.innerRouter, fmt.Sprintf("/referral/campaigns/%d", created.ID))
	var resp forms.CampaignResponse
	json.NewDecoder(w.Body).Decode(&resp)
	assert.Equal(entity.ReferralCampaignStatus_Ended, resp.Status)
}

// ========== Inner API: Trigger Reward ==========

func (suite *ReferralAPITestSuite) TestTriggerRewardAPI() {
	assert := suite.Assert()

	// Create campaign
	tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:              "Reward Campaign",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	})

	// Create referral code via model (inner API doesn't have a code creation endpoint)
	model := &marketing.ReferralModel{}
	codeResp, err := model.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	// CreditsAmount not set — handler reads from config by event type
	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/reward", &forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
	})
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.ReferralRewardResp
	err = json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.True(resp.Rewarded)
	assert.Equal(decimal.NewFromInt(5), resp.RewardAmount)
	assert.NotEmpty(resp.CreditsCode)
}

func (suite *ReferralAPITestSuite) TestTriggerRewardAPIInvalidInput() {
	assert := suite.Assert()

	// Missing required fields
	w := tests.PerformRequest(suite.innerRouter, "POST", "/referral/reward", map[string]interface{}{
		"referee_tenant_id": 100,
	})
	assert.Equal(http.StatusBadRequest, w.Code)
}

// ========== User API: Referral Code ==========

func (suite *ReferralAPITestSuite) TestGetReferralCodeAPI() {
	assert := suite.Assert()

	tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:              "Code Campaign",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	})

	w := tests.PerformGetRequest(suite.userRouter, "/tenants/1/referral/code")
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.ReferralCodeResponse
	err := json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Contains(resp.Code, "TIDB-1-")
	assert.Equal(0, resp.ReferralCount)
	assert.Contains(resp.ShareLink, "referral_code=")
}

func (suite *ReferralAPITestSuite) TestGetReferralCodeAPIIdempotent() {
	assert := suite.Assert()

	tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:              "Idempotent Campaign",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	})

	w1 := tests.PerformGetRequest(suite.userRouter, "/tenants/1/referral/code")
	w2 := tests.PerformGetRequest(suite.userRouter, "/tenants/1/referral/code")
	assert.Equal(http.StatusOK, w1.Code)
	assert.Equal(http.StatusOK, w2.Code)

	var resp1, resp2 forms.ReferralCodeResponse
	json.NewDecoder(w1.Body).Decode(&resp1)
	json.NewDecoder(w2.Body).Decode(&resp2)
	assert.Equal(resp1.Code, resp2.Code)
}

// ========== User API: Referral Stats ==========

func (suite *ReferralAPITestSuite) TestGetReferralStatsAPI() {
	assert := suite.Assert()

	// Create campaign + code + trigger reward via inner API
	tests.PerformRequest(suite.innerRouter, "POST", "/referral/campaigns", &forms.CreateCampaignReq{
		Name:              "Stats Campaign",
		TotalBudget:       decimal.NewFromInt(1000),
		IsWebsiteCampaign: true,
	})

	model := &marketing.ReferralModel{}
	codeResp, err := model.GetOrCreateReferralCode(1, true)
	assert.NoError(err)

	tests.PerformRequest(suite.innerRouter, "POST", "/referral/reward", &forms.ReferralRewardReq{
		ReferralCode:    codeResp.Code,
		RefereeTenantID: 100,
		EventType:       entity.ReferralEventType_Signup,
	})

	w := tests.PerformGetRequest(suite.userRouter, "/tenants/1/referral/stats")
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.ReferralStatsResponse
	err = json.NewDecoder(w.Body).Decode(&resp)
	assert.NoError(err)
	assert.Len(resp.Codes, 1)
	assert.Equal(codeResp.Code, resp.Codes[0].Code)
	assert.Equal(1, resp.TotalReferrals)
	assert.Equal(decimal.NewFromInt(5), resp.TotalEarned)
	assert.Len(resp.Events, 1)
	assert.Equal(entity.ReferralEventType_Signup, resp.Events[0].EventType)
	assert.True(resp.Events[0].Rewarded)
}

func (suite *ReferralAPITestSuite) TestGetReferralStatsAPIEmpty() {
	assert := suite.Assert()

	w := tests.PerformGetRequest(suite.userRouter, "/tenants/1/referral/stats")
	assert.Equal(http.StatusOK, w.Code)

	var resp forms.ReferralStatsResponse
	json.NewDecoder(w.Body).Decode(&resp)
	assert.Empty(resp.Events)
}
