package tests

import (
	"fmt"
	"testing"

	"github.com/google/uuid"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/eventbushandler"
	"github.com/pingcap/dbaas/billing2/handler/infrahandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/costexplorer"
	"github.com/pingcap/dbaas/billing2/model/payment/spend_limit"
	"github.com/pingcap/dbaas/billing2/utils/panicIf"
	"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/mock"
	"github.com/stretchr/testify/suite"
)

var spendLimitModel = &spend_limit.SpendLimitModel{}
var measureModel = &billing.MeasureModel{}

type SpendLimitSuite struct {
	tests.BillingTestSuite
}

func TestSpendLimitSuite(t *testing.T) {
	suite.Run(t, new(SpendLimitSuite))
}

func (suite *SpendLimitSuite) TestCRUDSpendLimitSetting() {
	assert := suite.Assert()
	tenantID := uint(1)
	_, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.NewFromInt(1),
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
	})
	assert.NoError(err)
	res, err := spendLimitModel.ListTenantSpendLimitSettings(tenantID, false)
	assert.NoError(err)
	assert.Len(res, 1)

	res, err = spendLimitModel.ListAllSpendLimitSettings(false)
	assert.NoError(err)
	assert.Len(res, 1)

	info := &res[0]
	assert.Equal(info.Interval, entity.SpendLimitInterval_Monthly)
	assert.Equal(info.CostType, entity.SpendLimitCostType_Total)
	assert.Equal(info.Limit, decimal.NewFromInt(1))

	info, err = spendLimitModel.GetSpendLimitSetting(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(info.Interval, entity.SpendLimitInterval_Monthly)
	assert.Equal(info.CostType, entity.SpendLimitCostType_Total)
	assert.Equal(info.Limit, decimal.NewFromInt(1))

	err = spendLimitModel.DeleteSpendLimitSetting(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)

	res, err = spendLimitModel.ListTenantSpendLimitSettings(tenantID, false)
	assert.NoError(err)
	assert.Len(res, 0)
}

func (suite *SpendLimitSuite) TestGetSpendLimitInfo() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	timeutils.SetMockNow(datainit.BillingDate)

	info, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.NewFromInt(1),
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
	})
	assert.NoError(err)
	err = datainit.CreateMeasures(tenantID)
	assert.NoError(err)
	res, err := spendLimitModel.GetSpendLimitInfo(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(res.StartDate, datainit.BillingDate)
	assert.Equal(res.Costs, decimal.NewFromInt(1))
	assert.Equal(res.Status, entity.SpendLimitStatus_Fit)
}

func (suite *SpendLimitSuite) TestSpendLimitInfoMatchesCostExplorerTotals() {
	assert := suite.Assert()
	tenantID := uint(1)
	_, err := datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	timeutils.SetMockNow(datainit.BillingDate)

	err = datainit.CreateMeasures(tenantID)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	costResult, err := costExplorerModel.GetMonthlyCost(tenantID, 0, datainit.BillingDate.Year(), int(datainit.BillingDate.Month()))
	assert.NoError(err)

	testCases := []struct {
		name     string
		bizData  forms.NormalBizData
		expected decimal.Decimal
	}{
		{
			name:     "raw_total",
			bizData:  forms.NormalBizData{},
			expected: costResult.TotalCosts.RawPrice,
		},
		{
			name: "discount_only",
			bizData: forms.NormalBizData{
				ApplyDiscount: true,
			},
			expected: costResult.TotalCosts.CostPrice,
		},
		{
			name: "credits_only",
			bizData: forms.NormalBizData{
				ApplyCredits: true,
			},
			expected: costResult.TotalCosts.RawPrice.Sub(costResult.CreditsCosts.CostPrice),
		},
		{
			name: "discount_and_credits",
			bizData: forms.NormalBizData{
				ApplyDiscount: true,
				ApplyCredits:  true,
			},
			expected: costResult.InvoiceCostsCosts.CostPrice,
		},
	}

	for _, tc := range testCases {
		bizData, err := spendLimitModel.MarshallNormalBizData(&tc.bizData)
		assert.NoError(err)
		setting, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
			Name:      tc.name,
			Interval:  entity.SpendLimitInterval_Monthly,
			CostType:  entity.SpendLimitCostType_Total,
			Limit:     decimal.NewFromInt(10),
			RangeType: entity.SpendLimitRangeType_Tenant,
			RangeID:   tenantID,
			BizType:   entity.SpendLimitBizType_Normal,
			BizData:   bizData,
		})
		assert.NoError(err)

		info, err := spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
		assert.NoError(err)
		assert.Equal(tc.expected, info.Costs, tc.name)
	}
}

func (suite *SpendLimitSuite) TestCheckSpendLimitStatus() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)

	billingDate := datainit.BillingDate
	timeutils.SetMockNow(billingDate)

	setting, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.RequireFromString("0.5"),
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
	})
	assert.NoError(err)

	err = datainit.CreateMeasures(tenantID)
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)

	info, err := spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Excessive, info.Status)

	setting.Limit = decimal.RequireFromString("1.5")
	newSetting, err := spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)

	assert.Equal(setting.Limit, newSetting.Limit)
	assert.Equal(entity.SpendLimitStatus_Fit, newSetting.Status)

	measureModel := &billing.MeasureModel{}
	projectID := uint(1)
	otherProjectID := uint(2)
	clusterID1 := uint(2)
	clusterID2 := uint(1)
	otherClusterID := uint(3)
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
		Labels:        map[string]string{constant.ProjectLabelKey: fmt.Sprint(projectID), constant.ClusterLabelKey: fmt.Sprint(clusterID1)},
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels = map[string]string{constant.ProjectLabelKey: fmt.Sprint(projectID), constant.ClusterLabelKey: fmt.Sprint(clusterID2)}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels = map[string]string{constant.ProjectLabelKey: fmt.Sprint(otherProjectID), constant.ClusterLabelKey: fmt.Sprint(otherClusterID)}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels = map[string]string{constant.ProjectLabelKey: fmt.Sprint(otherProjectID), constant.ClusterLabelKey: fmt.Sprint(otherClusterID)}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	setting.RangeType = entity.SpendLimitRangeType_Project
	setting.RangeID = projectID
	setting.Limit = decimal.RequireFromString("1.5")
	setting, err = spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Excessive, setting.Status)

	setting.RangeType = entity.SpendLimitRangeType_Project
	setting.RangeID = projectID
	setting.Limit = decimal.RequireFromString("2.5")
	setting, err = spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Fit, setting.Status)

	setting.RangeType = entity.SpendLimitRangeType_Cluster
	setting.RangeID = clusterID1
	setting.Limit = decimal.RequireFromString("0.5")
	setting, err = spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Excessive, setting.Status)

	setting.RangeType = entity.SpendLimitRangeType_Cluster
	setting.RangeID = clusterID1
	setting.Limit = decimal.RequireFromString("1.5")
	setting, err = spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Fit, setting.Status)
}

func (suite *SpendLimitSuite) TestCheckSpendLimitWaterMark() {
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	panicIf.Error(datainit.CreatePaymentMock(uint(1)))
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey: datainit.TestUnit,
	})
	panicIf.Error(err)
	panicIf.Error(datainit.CreatePricePackageAndBind(uint(1)))
	assert := suite.Assert()
	tenantID := uint(1)
	billingDate := datainit.BillingDate
	timeutils.SetMockNow(billingDate)
	setting, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.RequireFromString("1"),
		RangeType: entity.SpendLimitRangeType_Tenant,
		BizType:   entity.SpendLimitBizType_Normal,
		RangeID:   tenantID,
	})
	assert.NoError(err)
	for _, val := range []string{"0.9", "1", "0.75"} {
		err = (&entity.SpendLimitAlertThreshold{
			SpendingLimitID: setting.SpendLimitSettingID,
			BizType:         entity.SpendLimitBizType_Normal, Proportion: decimal.RequireFromString(val),
		}).Create(db.GetDB())
		assert.NoError(err)
	}

	unitCosts := forms.UnitCosts{
		CostID:        uuid.NewString(),
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	info, err := spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Excessive, info.Status)
	assert.Equal(decimal.NewFromInt(1), info.WaterMark)

	eb := eventbushandler.Get().(*tests.LocalEventBusHandlerImpl)
	var events []*eventbushandler.Event
	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 1)
	assert.Equal(decimal.NewFromInt(1), events[0].EventData.(*forms.SpendLimitThresholdAlert).Threshold)

	setting.Limit = decimal.RequireFromString("100")
	_, err = spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	unitCosts.CostID = uuid.NewString()
	unitCosts.Cost = 73
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Fit, info.Status)
	assert.Equal(decimal.NewFromInt(74), info.WaterMark)

	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 1)

	unitCosts.CostID = uuid.NewString()
	unitCosts.Cost = 1
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)

	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 2)
	assert.Equal(decimal.RequireFromString("0.75"), events[1].EventData.(*forms.SpendLimitThresholdAlert).Threshold)

	unitCosts.CostID = uuid.NewString()
	unitCosts.Cost = 14
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 2)

	unitCosts.CostID = uuid.NewString()
	unitCosts.Cost = 1
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)

	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 3)
	assert.Equal(decimal.RequireFromString("0.9"), events[2].EventData.(*forms.SpendLimitThresholdAlert).Threshold)

	setting.Limit = decimal.RequireFromString("95")
	_, err = spendLimitModel.UpdateSpendLimitSettings(setting.TenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)

	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)

	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 3)

	unitCosts.CostID = uuid.NewString()
	unitCosts.Cost = 6
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 4)
	assert.Equal(decimal.RequireFromString("1"), events[3].EventData.(*forms.SpendLimitThresholdAlert).Threshold)
	assert.Equal(decimal.NewFromInt(96), events[3].EventData.(*forms.SpendLimitThresholdAlert).WaterMark)

	// next month
	now := billingDate.AddDate(0, 1, 0)
	timeutils.SetMockNow(now)

	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 4)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(entity.SpendLimitStatus_Fit, info.Status)
	assert.Equal(decimal.NewFromInt(0), info.WaterMark)

	unitCosts.CostID = uuid.NewString()
	unitCosts.CostTimeStamp = now.Unix()
	unitCosts.Cost = 90
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	events = eb.GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 5)
	assert.Equal(decimal.RequireFromString("0.9"), events[4].EventData.(*forms.SpendLimitThresholdAlert).Threshold)
	assert.Equal(decimal.NewFromInt(90), events[4].EventData.(*forms.SpendLimitThresholdAlert).WaterMark)
}

func (suite *SpendLimitSuite) TestTotalSpendLimitAndWatermark() {
	datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	panicIf.Error(datainit.CreatePaymentMock(uint(1)))
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey: datainit.TestUnit,
	})
	panicIf.Error(err)
	panicIf.Error(datainit.CreatePricePackageAndBind(uint(1)))
	assert := suite.Assert()
	tenantID := uint(1)
	billingDate := datainit.BillingDate
	timeutils.SetMockNow(billingDate)
	datainit.CreateDefaultUserUnitCosts(tenantID, datainit.BillingDate, 30)
	setting, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.RequireFromString("1000"),
		RangeType: entity.SpendLimitRangeType_Tenant,
		BizType:   entity.SpendLimitBizType_Normal,
		RangeID:   tenantID,
		BizData:   "{\"apply_credits\":true,\"apply_discounts\":true}",
	})
	assert.NoError(err)
	err = spendLimitModel.CreateThresholds(tenantID, setting.SpendLimitSettingID, []*forms.SpendLimitThreshold{{Proportion: decimal.RequireFromString("0.05")}, {Proportion: decimal.RequireFromString("0.1")}, {Proportion: decimal.RequireFromString("0.2")}, {Proportion: decimal.RequireFromString("0.3")}})
	assert.NoError(err)
	info, err := spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(160), info.Costs)
	assert.Equal(decimal.NewFromInt(160), info.WaterMark)
	err = spendLimitModel.CheckSpendLimit(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	events := eventbushandler.Get().(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 0)
	setting.BizData = "{\"apply_credits\":false,\"apply_discounts\":false}"
	_, err = spendLimitModel.UpdateSpendLimitSettings(tenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(350), info.Costs)
	assert.Equal(decimal.NewFromInt(350), info.WaterMark)
	err = spendLimitModel.CheckSpendLimit(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	events = eventbushandler.Get().(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 0)
	setting.BizData = "{\"apply_credits\":false,\"apply_discounts\":true}"
	_, err = spendLimitModel.UpdateSpendLimitSettings(tenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(230), info.Costs)
	assert.Equal(decimal.NewFromInt(230), info.WaterMark)
	err = spendLimitModel.CheckSpendLimit(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	events = eventbushandler.Get().(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 0)
	setting.BizData = "{\"apply_credits\":true,\"apply_discounts\":false}"
	_, err = spendLimitModel.UpdateSpendLimitSettings(tenantID, setting.SpendLimitSettingID, setting)
	assert.NoError(err)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(280), info.Costs)
	assert.Equal(decimal.NewFromInt(280), info.WaterMark)
	err = spendLimitModel.CheckSpendLimit(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	events = eventbushandler.Get().(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 0)
	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	ent := entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		CostTimeStamp:         timeutils.GetNow().Unix(),
		Unit:                  datainit.AWSServerlessRUUnit,
		CostPrice:             decimal.NewFromInt(20),
		RawPrice:              decimal.NewFromInt(20),
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
		Internal:              false,
	}
	err = ent.Create(db.GetDB())
	assert.NoError(err)
	err = spendLimitModel.CheckSpendLimit(tenantID, info.SpendLimitSettingID)
	assert.NoError(err)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(300), info.Costs)
	assert.Equal(decimal.NewFromInt(300), info.WaterMark)
	events = eventbushandler.Get().(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(constant.SpendLimitThresholdEventType)
	assert.Len(events, 1)
	assert.Equal(events[0].EventData.(*forms.SpendLimitThresholdAlert).Threshold, decimal.RequireFromString("0.3"))
}

type MockCentralHandlerImpl struct {
	mock.Mock
	centralhandler.CentralHandler
}

func (m *MockCentralHandlerImpl) SendTenantOwnerEmailOnce(tenantID uint, key string, templateID string, vars map[string]string) error {
	m.MethodCalled("SendTenantOwnerEmailOnce", tenantID, key, templateID, vars)
	return nil
}

const mockProjectName = "mock_project_name"

func (m *MockCentralHandlerImpl) GetProjectName(projectID uint) (string, error) {
	return mockProjectName, nil
}

func (m *MockCentralHandlerImpl) GetProjectInfo(projectID uint) (*forms.Project, error) {
	return &forms.Project{
		ID:   projectID,
		Name: mockProjectName,
	}, nil
}

type MockInfraHandlerImpl struct {
	mock.Mock
	infrahandler.InfraHandlerImpl
}

const mockProjectID = 5

func (m *MockInfraHandlerImpl) GetCluster(tenantID, clusterID uint) (*forms.Cluster, error) {
	return &forms.Cluster{
		ID:        clusterID,
		Name:      constant.UnknownClusterName,
		TenantID:  tenantID,
		ProjectID: mockProjectID,
	}, nil
}

func (suite *SpendLimitServerlessSuite) TestSendSpendLimitEmail() {
	mockCentral := &MockCentralHandlerImpl{}
	mockCentral.On("SendTenantOwnerEmailOnce", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
	mockInfra := &MockInfraHandlerImpl{}
	centralhandler.SetCentralHandler(mockCentral)
	infrahandler.Set(mockInfra)
	var err error
	assert := suite.Assert()
	tenantID := uint(1)
	setting, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.RequireFromString("1"),
		RangeType: entity.SpendLimitRangeType_Tenant,
		RangeID:   tenantID,
	})
	assert.NoError(err)
	info, err := spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	threshold := decimal.RequireFromString("0.75")
	event := &eventbushandler.Event{
		EventID:   uuid.NewString(),
		EventType: constant.SpendLimitThresholdEventType,
		EventData: &forms.SpendLimitThresholdAlert{
			Threshold:      threshold,
			SpendLimitInfo: *info,
		},
	}
	err = spendLimitModel.HandleSpendLimitEmail(event)
	assert.NoError(err)
	clusterID := uint(1)
	mockCentral.AssertNotCalled(suite.T(), "SendTenantOwnerEmailOnce")
	q := forms.SpendLimitSetting{
		Interval:  entity.SpendLimitInterval_Monthly,
		CostType:  entity.SpendLimitCostType_Total,
		Limit:     decimal.RequireFromString("0.5"),
		RangeType: entity.SpendLimitRangeType_Cluster,
		RangeID:   clusterID,
		BizType:   entity.SpendLimitBizType_ServerlessCluster,
		BizData:   zeroFreeCreditsServerlessBizData,
	}
	setting, err = spendLimitModel.CreateSpendLimitSetting(tenantID, &q)
	assert.NoError(err)
	info, err = spendLimitModel.GetSpendLimitInfo(tenantID, setting.SpendLimitSettingID)
	assert.NoError(err)
	event = &eventbushandler.Event{
		EventID:   uuid.NewString(),
		EventType: constant.SpendLimitThresholdEventType,
		EventData: &forms.SpendLimitThresholdAlert{
			Threshold:      threshold,
			SpendLimitInfo: *info,
		},
	}
	err = spendLimitModel.HandleSpendLimitEmail(event)
	assert.NoError(err)
	emailID := fmt.Sprintf(constant.SpendLimitEmailKeyPattern, info.SpendLimitSettingID, event.EventID)
	mockCentral.AssertCalled(suite.T(), "SendTenantOwnerEmailOnce", tenantID, emailID, constant.ServerlessSpendLimitEmailTemplateID, map[string]string{
		constant.ServerlessSpendLimitEmailThresholdKey:            "75",
		constant.ServerlessSpendLimitEmailThresholdClusterNameKey: constant.UnknownClusterName,
		constant.ServerlessSpendLimitEmailThresholdClusterIDKey:   fmt.Sprint(clusterID),
		constant.ServerlessSpendLimitEmailThresholdProjectIDKey:   fmt.Sprint(mockProjectID),
		constant.ServerlessSpendLimitEmailThresholdProjectNameKey: mockProjectName,
	})
}
