package tests

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/pingcap/dbaas/billing2/config"
	"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/cachehandler"
	"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/marketing"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/pp"
	"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 CostExplorerSuite struct {
	tests.BillingTestSuite
}

func TestCostExplorerSuite(t *testing.T) {
	suite.Run(t, new(CostExplorerSuite))
}

func (suite *CostExplorerSuite) SetupTest() {
	assert := suite.Assert()
	var err error
	suite.BillingTestSuite.SetupTest()
	_, err = datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	datainit.CreateCategoryLabels()
	cachehandler.InitWithLocal()
	config.Get().Set(config.EnableCache, true)
}

func (suite *CostExplorerSuite) TearDownTest() {
	suite.BillingTestSuite.SetupTest()
	config.Get().Set(config.EnableCache, false)
}

func newCreditsPackageWhihTime(tenantID uint, credits int64, date time.Time) (ret *forms.CreditsPackage, err error) {
	generalUnitFilter, err := (&unit_filter.Model{}).CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	billingConfigID := uint(1)
	creditsPackage := &forms.CreditsPackage{
		StartDate:             timeutils.GetMonthDate(date),
		ExpiredDate:           date.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(credits),
		Usage:                 entity.CreditsPackageType_General,
		UnitFilters:           []*forms.UnitFilterSlim{{TypeID: generalUnitFilter.FilterID}},
		DiscountEnabled:       true,
		BindedBillingConfigID: &billingConfigID,
	}
	creditsPackage, err = (&marketing.CreditsPackageModel{}).CreateCreditsPackage(tenantID, creditsPackage)
	return creditsPackage, err

}
func (suite *CostExplorerSuite) TestCostExplorer() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)
	packageInfo, err := newCreditsPackageWhihTime(tenantID, 2, billingDate)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(2),
		PriceType: entity.PriceType_Normal,
	})

	bindedID1 := *packageInfo.BindedPackageID
	_ = bindedID1
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.CalculateCost(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
	})

	assert.NoError(err)
	assert.Equal(res.CreditsCosts.CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.UnbindedInvoiceCosts.CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.BindedInvoiceCosts.CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(3))
	assert.Equal(res.TotalCosts.CostPrice, decimal.NewFromInt(5))
}

func (suite *CostExplorerSuite) TestCostExplorerWitchCache() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)
	packageInfo, err := newCreditsPackageWhihTime(tenantID, 2, billingDate)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(2),
		PriceType: entity.PriceType_Normal,
	})

	bindedID1 := *packageInfo.BindedPackageID
	_ = bindedID1
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Labels:        map[string]string{constant.ClusterLabelKey: "1"},
		Cost:          1,
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.CalculateCostWithCache(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "1"},
	})

	assert.NoError(err)
	assert.Equal(res.TotalCosts.CostPrice, decimal.NewFromInt(4))
	unknownKey := "unknown_key"
	unitCosts.Labels[unknownKey] = "!"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	res, err = costExplorerModel.CalculateCostWithCache(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "1"},
	})
	assert.Equal(res.TotalCosts.CostPrice, decimal.NewFromInt(4))
	res, err = costExplorerModel.CalculateCost(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "1"},
	})
	assert.Equal(res.TotalCosts.CostPrice, decimal.NewFromInt(6))
	res, err = costExplorerModel.CalculateCostWithCache(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "1", unknownKey: "!"},
	})
	assert.Equal(res.TotalCosts.CostPrice, decimal.NewFromInt(2))
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	res, err = costExplorerModel.CalculateCostWithCache(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "1", unknownKey: "!"},
	})
	assert.Equal(res.TotalCosts.CostPrice, decimal.NewFromInt(4))
}

func (suite *CostExplorerSuite) TestCostExplorerInternal() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	u1, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitA",
		Category:  "category_A",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	u2, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitB",
		Category:  "category_B",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)
	billingConfigID := uint(1)
	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       true,
		Internal:              false,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})
	_, err = newCreditsPackageWhihTime(uint(1), 10, billingDate)

	assert.NoError(err)

	unitCosts1 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u1.UnitKey,
		Cost:          5,
	}
	unitCosts2 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u2.UnitKey,
		Cost:          5,
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts1)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts1)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.CalculateCost(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Units:     []string{u1.UnitKey, u2.UnitKey},
	})

	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), res.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(20), res.TotalCosts.CostPrice)

	res, err = costExplorerModel.CalculateCost(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Units:     []string{u1.UnitKey},
	})

	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), res.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.TotalCosts.CostPrice)
}

func (suite *CostExplorerSuite) TestCostExplorerInternalV2() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	u1, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitA",
		Category:  "category_A",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	u2, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitB",
		Category:  "category_B",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)
	billingConfigID := uint(1)
	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       true,
		Internal:              false,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(uint(1), 10, billingDate)
	assert.NoError(err)
	freeQuota := decimal.NewFromInt(10)
	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       false,
		Internal:              true,
		ChargeCredits:         &freeQuota,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	unitCosts1 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u1.UnitKey,
		Cost:          5,
		Labels:        map[string]string{constant.ClusterLabelKey: "1"},
	}
	unitCosts2 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u2.UnitKey,
		Cost:          5,
		Labels:        map[string]string{constant.ClusterLabelKey: "2"},
	}

	c1, err := measureModel.CostUnits(tenantID, &unitCosts1)
	assert.NoError(err)
	assert.True(c1.Internal)
	c2, err := measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)
	assert.True(c2.Internal)
	c3, err := measureModel.CostUnits(tenantID, &unitCosts1)
	assert.NoError(err)
	assert.False(c3.Internal)
	_, err = measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts1)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Units:     []string{u1.UnitKey, u2.UnitKey},
	})
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), res.ExternalCosts.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.ExternalCosts.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.ExternalCosts.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.ExternalCosts.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(20), res.ExternalCosts.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.InternalCosts.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.InternalCosts.TotalCosts.CostPrice)

	res, err = costExplorerModel.CalculateCostV2(tenantID, []uint{1}, &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Units:     []string{u1.UnitKey},
	})
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), res.ExternalCosts.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.ExternalCosts.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.ExternalCosts.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.ExternalCosts.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.ExternalCosts.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.InternalCosts.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.InternalCosts.TotalCosts.CostPrice)

	res, err = costExplorerModel.CalculateCostV2(tenantID, []uint{1}, &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "2"},
	})
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(5), res.ExternalCosts.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.ExternalCosts.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.ExternalCosts.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.ExternalCosts.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.ExternalCosts.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.InternalCosts.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InternalCosts.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(5), res.InternalCosts.TotalCosts.CostPrice)

	_, err = measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)

	res, err = costExplorerModel.CalculateCostV2(tenantID, []uint{1}, &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "2"},
	})
	assert.Equal(decimal.NewFromInt(15), res.ExternalCosts.TotalCosts.CostPrice)

	res, err = costExplorerModel.CalculateCostV2(tenantID, []uint{1}, &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Labels:    map[string]string{constant.ClusterLabelKey: "1"},
	})
	assert.Equal(decimal.NewFromInt(10), res.ExternalCosts.TotalCosts.CostPrice)

	monthlyRes, err := costExplorerModel.GetMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	spendLimitMonthlyRes, err := costExplorerModel.GetMonthlySpendLimitCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.Equal(monthlyRes.TotalCosts.RawPrice, spendLimitMonthlyRes.TotalRawPrice)
	assert.Equal(monthlyRes.TotalCosts.CostPrice, spendLimitMonthlyRes.TotalCostPrice)
	assert.Equal(monthlyRes.InvoiceCostsCosts.CostPrice, spendLimitMonthlyRes.InvoiceCostPrice)
	assert.Equal(monthlyRes.CreditsCosts.CostPrice, spendLimitMonthlyRes.CreditsCostPrice)

	clusterRes, err := costExplorerModel.GetClusterMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), 1)
	assert.NoError(err)
	clusterSpendLimitRes, err := costExplorerModel.GetClusterMonthlySpendLimitCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), 1)
	assert.NoError(err)
	assert.Equal(clusterRes.TotalCosts.RawPrice, clusterSpendLimitRes.TotalRawPrice)
	assert.Equal(clusterRes.TotalCosts.CostPrice, clusterSpendLimitRes.TotalCostPrice)
	assert.Equal(clusterRes.InvoiceCostsCosts.CostPrice, clusterSpendLimitRes.InvoiceCostPrice)
	assert.Equal(clusterRes.CreditsCosts.CostPrice, clusterSpendLimitRes.CreditsCostPrice)
}

func (suite *CostExplorerSuite) TestTenantCustomerSummaryUsesStreamingAggregation() {
	assert := suite.Assert()
	// The customer summary resolves tenant names via the central handler; install the in-memory
	// mock so the test does not depend on global handler state leaked from other tests.
	tests.MockCentralHandler()
	tenantIDs := []uint{1, 2}
	_, err := datainit.CreateBillingConfig(2, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	for _, tenantID := range tenantIDs {
		datainit.CreateDefaultUserUnitCosts(tenantID, datainit.BillingDate, 12)
	}

	costExplorerModel := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate:                datainit.BillingDate,
		EndDate:                  datainit.BillingDate.AddDate(0, 0, 12),
		DisableAttachProjectName: true,
		DisableAttachClusterName: true,
	}
	costs, summaryByCustomer, err := costExplorerModel.CalculateCostV2MultiTenantsWithTenantCustomerSummary(tenantIDs, nil, query)
	assert.NoError(err)

	assert.Empty(costs.AllCosts)
	assert.Equal(decimal.NewFromInt(172), costs.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(268), costs.TotalCosts.RawPrice)
	assert.Len(summaryByCustomer.Customers, 2)

	for _, customer := range summaryByCustomer.Customers {
		assert.Contains(tenantIDs, customer.CustomerID)
		assert.Equal(decimal.NewFromInt(134), customer.RunningTotal)
		assert.Equal(decimal.NewFromInt(48), customer.Discounts)
		assert.Equal(decimal.NewFromInt(70), customer.Credits)
		assert.Equal(decimal.NewFromInt(16), customer.TotalCost)
	}
}

func (suite *CostExplorerSuite) TestCostExplorerStreamingSplitsByTenantCluster() {
	assert := suite.Assert()
	const tenantID uint = 1
	billingDate := datainit.BillingDate
	billingConfig, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfig(tenantID)
	assert.NoError(err)
	unitKey := "cluster-split-" + strings.ReplaceAll(uuid.NewString(), "-", "")
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   unitKey,
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
		Category:  "Node Compute Cost",
	})
	assert.NoError(err)

	createCost := func(clusterID string, rawPrice, costPrice decimal.Decimal, internal bool) {
		labels := map[string]string{constant.ProjectLabelKey: fmt.Sprint(datainit.DefaultUnitCostsProjectID)}
		if clusterID != "" {
			labels[constant.ClusterLabelKey] = clusterID
		}
		err := (&entity.UserUnitCost{
			CostID:                uuid.NewString(),
			TenantID:              tenantID,
			BindedBillingConfigID: billingConfig.BillingConfigID,
			CostTimeStamp:         billingDate.Unix(),
			Unit:                  unitKey,
			Cost:                  1,
			CostPrice:             costPrice,
			RawPrice:              rawPrice,
			Labels:                labels,
			ConsumedSource:        entity.ConsumedSource_Invoice,
			Status:                entity.UserUnitCostStatus_DONE,
			Internal:              internal,
		}).Create(db.GetDB())
		assert.NoError(err)
	}
	createCost("101", decimal.NewFromFloat(10.009), decimal.NewFromFloat(9.009), false)
	createCost("102", decimal.NewFromFloat(20.009), decimal.NewFromFloat(18.009), false)
	createCost("", decimal.NewFromFloat(5.009), decimal.NewFromFloat(4.009), false)
	createCost("102", decimal.NewFromInt(100), decimal.NewFromInt(100), true)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate:                billingDate,
		EndDate:                  billingDate.AddDate(0, 0, 1),
		DisableAttachProjectName: true,
		DisableAttachClusterName: true,
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{billingConfig.BillingConfigID}, query)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(35), res.ExternalCosts.TotalCosts.RawPrice)
	assert.Equal(decimal.NewFromInt(31), res.ExternalCosts.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(100), res.InternalCosts.TotalCosts.CostPrice)

	clusterQuery := *query
	clusterQuery.Labels = map[string]string{constant.ClusterLabelKey: "101"}
	clusterRes, err := costExplorerModel.CalculateCostV2(tenantID, []uint{billingConfig.BillingConfigID}, &clusterQuery)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), clusterRes.ExternalCosts.TotalCosts.RawPrice)
	assert.Equal(decimal.NewFromInt(9), clusterRes.ExternalCosts.TotalCosts.CostPrice)
}

// TestSpendLimitCostBoundaryConditions verifies the streamed spend-limit summaries stay
// consistent with the cost-explorer totals across tenant/project/customer scopes and at the
// empty/zero boundaries, and that internal costs never leak into the spend-limit numbers.
func (suite *CostExplorerSuite) TestSpendLimitCostBoundaryConditions() {
	assert := suite.Assert()
	const tenantID uint = 1
	datainit.CreateDefaultUserUnitCosts(tenantID, datainit.BillingDate, 12)
	year, month := datainit.BillingDate.Year(), int(datainit.BillingDate.Month())
	model := &costexplorer.CostExplorerModel{}

	// Tenant scope: spend limit summary matches the external monthly totals.
	monthly, err := model.GetMonthlyCost(tenantID, 0, year, month)
	assert.NoError(err)
	spendLimit, err := model.GetMonthlySpendLimitCost(tenantID, 0, year, month)
	assert.NoError(err)
	assert.Equal(monthly.TotalCosts.RawPrice, spendLimit.TotalRawPrice)
	assert.Equal(monthly.TotalCosts.CostPrice, spendLimit.TotalCostPrice)
	assert.Equal(monthly.InvoiceCostsCosts.CostPrice, spendLimit.InvoiceCostPrice)
	assert.Equal(monthly.CreditsCosts.CostPrice, spendLimit.CreditsCostPrice)
	// The dataset contains both credits and invoice external costs plus internal costs;
	// total external cost must equal invoice + credits with no internal leakage.
	assert.True(spendLimit.TotalRawPrice.IsPositive())
	assert.True(spendLimit.InvoiceCostPrice.IsPositive())
	assert.True(spendLimit.CreditsCostPrice.IsPositive())
	assert.Equal(spendLimit.TotalCostPrice, spendLimit.InvoiceCostPrice.Add(spendLimit.CreditsCostPrice))

	// Project scope: spend limit summary matches the project monthly totals.
	projectMonthly, err := model.GetProjectMonthlyCost(tenantID, 0, year, month, datainit.DefaultUnitCostsProjectID)
	assert.NoError(err)
	projectSpendLimit, err := model.GetProjectMonthlySpendLimitCost(tenantID, 0, year, month, datainit.DefaultUnitCostsProjectID)
	assert.NoError(err)
	assert.Equal(projectMonthly.TotalCosts.RawPrice, projectSpendLimit.TotalRawPrice)
	assert.Equal(projectMonthly.InvoiceCostsCosts.CostPrice, projectSpendLimit.InvoiceCostPrice)
	assert.Equal(projectMonthly.CreditsCosts.CostPrice, projectSpendLimit.CreditsCostPrice)

	// Customer scope with no matching MSP costs: both paths return zero.
	customerMonthly, err := model.GetCustomerMonthlyCost(tenantID, 0, year, month, 999)
	assert.NoError(err)
	customerSpendLimit, err := model.GetCustomerMonthlySpendLimitCost(tenantID, 0, year, month, 999)
	assert.NoError(err)
	assert.True(customerSpendLimit.TotalRawPrice.IsZero())
	assert.Equal(customerMonthly.TotalCosts.RawPrice, customerSpendLimit.TotalRawPrice)

	// Empty period: a zeroed summary is returned without panicking on the nil-result boundary.
	empty, err := model.GetMonthlySpendLimitCost(tenantID, 0, year-2, month)
	assert.NoError(err)
	assert.True(empty.TotalRawPrice.IsZero())
	assert.True(empty.TotalCostPrice.IsZero())
	assert.True(empty.InvoiceCostPrice.IsZero())
	assert.True(empty.CreditsCostPrice.IsZero())
}

// TestTenantCustomerSummaryExcludesInternalOnlyTenant ensures the streaming customer summary
// does not surface a tenant whose costs are entirely internal, and that such internal costs
// never leak into the external totals.
func (suite *CostExplorerSuite) TestTenantCustomerSummaryExcludesInternalOnlyTenant() {
	assert := suite.Assert()
	// The customer summary resolves tenant names via the central handler; install the in-memory
	// mock so the test does not depend on global handler state leaked from other tests.
	tests.MockCentralHandler()
	_, err := datainit.CreateBillingConfig(2, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	datainit.CreateDefaultUserUnitCosts(1, datainit.BillingDate, 12)

	cfgID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(2)
	assert.NoError(err)
	internalCost := entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              2,
		BindedBillingConfigID: cfgID,
		CostTimeStamp:         datainit.BillingDate.Unix(),
		Unit:                  datainit.AWSNodeUnit,
		CostPrice:             decimal.NewFromInt(5),
		RawPrice:              decimal.NewFromInt(7),
		Labels:                map[string]string{constant.ClusterLabelKey: "1", constant.ProjectLabelKey: "1"},
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
		Internal:              true,
	}
	assert.NoError(internalCost.Create(db.GetDB()))

	model := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate:                datainit.BillingDate,
		EndDate:                  datainit.BillingDate.AddDate(0, 0, 12),
		DisableAttachProjectName: true,
		DisableAttachClusterName: true,
	}

	tenant1Only, _, err := model.CalculateCostV2MultiTenantsWithTenantCustomerSummary([]uint{1}, nil, query)
	assert.NoError(err)

	costs, summary, err := model.CalculateCostV2MultiTenantsWithTenantCustomerSummary([]uint{1, 2}, nil, query)
	assert.NoError(err)
	// Only tenant 1 (with external costs) is reported; tenant 2 is internal-only.
	assert.Len(summary.Customers, 1)
	assert.Equal(uint(1), summary.Customers[0].CustomerID)
	// Tenant 2's internal cost does not leak into the external totals.
	assert.Equal(tenant1Only.TotalCosts.RawPrice, costs.TotalCosts.RawPrice)
	assert.Equal(tenant1Only.TotalCosts.CostPrice, costs.TotalCosts.CostPrice)
}

// TestDisabledTenantSkipsClusterNameAttachment guards the parity rule that tenants listed in
// DisableClusterDisplayTenants never get cluster names attached, even when label-preserving
// (pre-cutoff) queries still stream rows through the attachment path.
//
// The costs intentionally carry only a clusterID label (no projectID) and project-name
// attachment is disabled, so cluster-name attachment resolves entirely through the offline
// infra stub (EnableInfraHandler=false) without any central-service calls.
func (suite *CostExplorerSuite) TestDisabledTenantSkipsClusterNameAttachment() {
	assert := suite.Assert()
	const tenantID uint = 1
	billingConfig, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfig(tenantID)
	assert.NoError(err)
	unitKey := "cluster-attach-" + strings.ReplaceAll(uuid.NewString(), "-", "")
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   unitKey,
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
		Category:  "Node Compute Cost",
	})
	assert.NoError(err)
	err = (&entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfig.BillingConfigID,
		CostTimeStamp:         datainit.BillingDate.Unix(),
		Unit:                  unitKey,
		Cost:                  1,
		CostPrice:             decimal.NewFromInt(1),
		RawPrice:              decimal.NewFromInt(1),
		Labels:                map[string]string{constant.ClusterLabelKey: "1"},
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
	}).Create(db.GetDB())
	assert.NoError(err)

	config.Get().Set(config.BillsDisplayProjectAndClusterNameWithID, true)
	config.Get().Set(config.EnableUseMgmtGetClusterInfo, false)
	config.Get().Set(config.EnableInfraHandler, false)
	// The trends suite may have globally installed a mock infra handler whose GetCluster is nil;
	// install the real handler so cluster lookups resolve to the offline stub.
	prevInfra := infrahandler.Get()
	infrahandler.Set(&infrahandler.InfraHandlerImpl{})
	defer func() {
		infrahandler.Set(prevInfra)
		config.Get().Set(config.BillsDisplayProjectAndClusterNameWithID, false)
		config.Get().Set(config.EnableUseMgmtGetClusterInfo, true)
		config.Get().Set(config.EnableInfraHandler, true)
		config.Get().Set(config.DisableClusterDisplayTenants, []string{})
	}()

	model := &costexplorer.CostExplorerModel{}
	// StartDate is before the 2026-02-01 cutoff, so labels stay in the GROUP BY and cluster-name
	// attachment is still attempted even for disabled tenants.
	query := &forms.CostExplorerQueryV2{
		StartDate:                datainit.BillingDate,
		EndDate:                  datainit.BillingDate.AddDate(0, 0, 1),
		DisableAttachProjectName: true,
	}

	hasClusterName := func(tenantIDs []uint) bool {
		seen := false
		err := model.ForEachCostSummary(tenantIDs, nil, query, func(cost forms.UnitCostSummary) error {
			if _, ok := cost.Labels[constant.ClusterNameLabelKey]; ok {
				seen = true
			}
			return nil
		})
		assert.NoError(err)
		return seen
	}

	// Baseline: cluster display enabled, so the cluster name is attached.
	config.Get().Set(config.DisableClusterDisplayTenants, []string{})
	assert.True(hasClusterName([]uint{tenantID}), "cluster name should be attached when the tenant is not disabled")

	// Disabled tenant: cluster name attachment must be skipped.
	config.Get().Set(config.DisableClusterDisplayTenants, []string{fmt.Sprint(tenantID)})
	assert.False(hasClusterName([]uint{tenantID}), "cluster name must not be attached for tenants in DisableClusterDisplayTenants")
}

func (suite *CostExplorerSuite) TestCostExplorerByMonth() {
	assert := suite.Assert()
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)
	creditsPackage, err := newCreditsPackageWhihTime(tenantID, 2, billingDate)

	bindedID1 := *creditsPackage.BindedPackageID
	_ = bindedID1
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Category:  "category_B",
		Price:     decimal.NewFromInt(2),
		PriceType: entity.PriceType_Normal,
	})

	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetMonthlyCostWithCache(tenantID, 1, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.Equal(res.CreditsCosts.CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.UnbindedInvoiceCosts.CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.BindedInvoiceCosts.CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(3))

	log.Info("Cache Begin")

	// should match cache
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	res, err = costExplorerModel.GetMonthlyCostWithCache(tenantID, 1, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(3))
}

func (suite *CostExplorerSuite) TestCostExplorerByMonthWithLabels() {
	assert := suite.Assert()
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})

	_, err = newCreditsPackageWhihTime(tenantID, 1, billingDate)

	assert.NoError(err)

	projectID1 := uint(1)
	projectID2 := uint(2)
	clusterID1 := uint(1)
	clusterID2 := uint(2)
	clusterID3 := uint(3)
	clusterID4 := uint(4)
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
		Labels:        map[string]string{constant.ProjectLabelKey: fmt.Sprint(projectID1), constant.ClusterLabelKey: fmt.Sprint(clusterID1)},
	}

	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels = map[string]string{constant.ProjectLabelKey: fmt.Sprint(projectID1), constant.ClusterLabelKey: fmt.Sprint(clusterID2)}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels = map[string]string{constant.ProjectLabelKey: fmt.Sprint(projectID2), constant.ClusterLabelKey: fmt.Sprint(clusterID3)}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels = map[string]string{constant.ProjectLabelKey: fmt.Sprint(projectID2), constant.ClusterLabelKey: fmt.Sprint(clusterID4)}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetProjectMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), projectID1)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(2), res.TotalCosts.CostPrice)
	res, err = costExplorerModel.GetClusterMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), clusterID1)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(1), res.TotalCosts.CostPrice)
	res, err = costExplorerModel.GetClusterMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), clusterID2)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(1), res.TotalCosts.CostPrice)
	res, err = costExplorerModel.GetProjectMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), projectID2)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(2), res.TotalCosts.CostPrice)
	assert.NoError(err)
	res, err = costExplorerModel.GetClusterMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), clusterID3)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(1), res.TotalCosts.CostPrice)
	res, err = costExplorerModel.GetClusterMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()), clusterID4)
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(1), res.TotalCosts.CostPrice)
}

func (suite *CostExplorerSuite) TestCostExplorerByBindedPackageID() {
	assert := suite.Assert()
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	generalUnitFilter, err := (&unit_filter.Model{}).CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	billingConfigID := uint(1)
	totalCreditsPackages := 3
	totalCredits := 0
	var credits []*forms.CreditsPackage
	for i := 0; i != totalCreditsPackages; i++ {
		creditsPackage := &forms.CreditsPackage{
			StartDate:             timeutils.GetMonthDate(billingDate),
			ExpiredDate:           billingDate.AddDate(0, 1, 0),
			TotalCredits:          decimal.NewFromInt(int64(i + 1)),
			Usage:                 entity.CreditsPackageType_General,
			UnitFilters:           []*forms.UnitFilterSlim{{TypeID: generalUnitFilter.FilterID}},
			DiscountEnabled:       true,
			BindedBillingConfigID: &billingConfigID,
		}
		creditsPackage, err = (&marketing.CreditsPackageModel{}).CreateCreditsPackage(tenantID, creditsPackage)
		totalCredits += i + 1
		credits = append(credits, creditsPackage)
	}
	for i := 0; i != totalCredits; i++ {
		unitCosts := forms.UnitCosts{
			CostTimeStamp: billingDate.Unix(),
			Unit:          datainit.TestUnit,
			Cost:          1,
		}
		_, err = measureModel.CostUnits(tenantID, &unitCosts)
		assert.NoError(err)
	}
	costExplorerModel := &costexplorer.CostExplorerModel{}
	for i := 0; i != totalCreditsPackages; i++ {
		start := timeutils.GetMonthDateByYearMonth(billingDate.Year(), int(billingDate.Month()))
		query := &forms.CostExplorerQueryV2{
			StartDate:        start,
			EndDate:          start.AddDate(0, 1, 0),
			BindedPackageIDs: []uint{*credits[i].BindedPackageID},
		}
		res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
		assert.NoError(err)
		assert.Equal(decimal.NewFromInt(int64(i+1)), res.ExternalCosts.CreditsCosts.CostPrice)
	}
	start := timeutils.GetMonthDateByYearMonth(billingDate.Year(), int(billingDate.Month()))
	query := &forms.CostExplorerQueryV2{
		StartDate:        start,
		EndDate:          start.AddDate(0, 1, 0),
		BindedPackageIDs: []uint{*credits[0].BindedPackageID, *credits[1].BindedPackageID},
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
	assert.Equal(decimal.NewFromInt(3), res.ExternalCosts.CreditsCosts.CostPrice)
}

func (suite *CostExplorerSuite) TestCostExplorerPrecision() {
	assert := suite.Assert()
	fmt.Print(config.Get().GetBool(config.EnableCache))
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitA",
		Category:  "category_A",
		Price:     decimal.RequireFromString("0.001"),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitB",
		Category:  "category_B",
		Price:     decimal.RequireFromString("0.001"),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	datainit.CreatePaymentMock(uint(1))

	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	pp.Print(err)

	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix() + 10,
		Unit:          "test_unitA",
		Cost:          1004,
		Labels:        map[string]string{},
	}

	unitCosts.Labels["project"] = "A"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "B"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "C"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	unitCosts.Unit = "test_unitB"

	unitCosts.Labels["project"] = "A"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "B"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	unitCosts.Labels["project"] = "C"
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetMonthlyCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()))

	assert.NoError(err)
	// actual value should be 6.024 , but we truncate mid level data first, and sum up to avoid precision error
	assert.Equal(res.CreditsCosts.CostPrice, decimal.NewFromInt(6))
	assert.Equal(res.CreditsCosts.CategoryUnitsCosts["category_A"].CostPrice, decimal.NewFromInt(3))
	assert.Equal(res.CreditsCosts.CategoryUnitsCosts["category_B"].CostPrice, decimal.NewFromInt(3))

	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["A"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["B"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["C"].CostPrice, decimal.NewFromInt(2))

	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["A"].CategoryUnitsCosts["category_A"].CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.CreditsCosts.LabelsUnitsCosts["project"]["A"].CategoryUnitsCosts["category_B"].CostPrice, decimal.NewFromInt(1))

	spendLimitRes, err := costExplorerModel.GetMonthlySpendLimitCost(tenantID, 1, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.Equal(res.TotalCosts.RawPrice, spendLimitRes.TotalRawPrice)
	assert.Equal(res.TotalCosts.CostPrice, spendLimitRes.TotalCostPrice)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, spendLimitRes.InvoiceCostPrice)
	assert.Equal(res.CreditsCosts.CostPrice, spendLimitRes.CreditsCostPrice)
}

func (suite *CostExplorerSuite) TestGetServiceName() {
	category, categoryTransfer := "Node Storage Cost", "Data Transfer Cost - AWS Load Balancing"
	categoryLabels := datainit.GetCategoryLabel()
	categoryItemInfo, ok := categoryLabels[category]
	detailslCosts := entity.CostsDetailsQuery{
		Labels: map[string]string{
			"component": "COMPONENT_TYPE_TIKV",
		},
	}

	fmt.Println(categoryLabels[categoryTransfer].Labels[categoryLabels[categoryTransfer].LabelKey])
	if ok {
		if categoryItemInfo.UseCateGory {

			fmt.Println(categoryItemInfo.Labels[category])

		} else {
			getLabelsKey := detailslCosts.Labels[categoryItemInfo.LabelKey]
			showName := categoryItemInfo.Labels[getLabelsKey]
			fmt.Println(showName)

			fmt.Println(categoryItemInfo.ShowServicePrefix, showName)
		}

	} else {
		fmt.Println(strings.TrimRight(category, "Cost"))
	}

}

const betaCat = "Test Beta Category"

func (suite *CostExplorerSuite) TestMeasureCostSummary() {
	assert := suite.Assert()
	costs := []forms.UnitCostSummary{
		{
			Unit:      "TestA1",
			CostPrice: decimal.NewFromInt(1),
			RawPrice:  decimal.NewFromInt(2),
			Labels:    map[string]string{"project": "test1"},
		},
		{
			Unit:      "TestA2",
			CostPrice: decimal.NewFromInt(1),
			RawPrice:  decimal.NewFromInt(2),
			Labels:    map[string]string{"project": "test2"},
		},
		{
			Unit:      "TestB1",
			CostPrice: decimal.NewFromInt(1),
			RawPrice:  decimal.NewFromInt(1),
		},
		{
			Unit:      "TestNo",
			CostPrice: decimal.NewFromInt(1),
			RawPrice:  decimal.NewFromInt(2),
		},
		{
			Unit:      "TestCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
			Labels:    map[string]string{"component": "COMPONENT_TYPE_TIDB"},
		},
		{
			Unit:      "TestTransfer",
			CostPrice: decimal.NewFromInt(111),
			RawPrice:  decimal.NewFromInt(222),
		},
		{
			Unit:      "TestCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
			Labels:    map[string]string{"component": "COMPONENT_TYPE_TIKV"},
		},
		{
			Unit:      "TestBackupCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
			Labels:    map[string]string{"component": "Replication Cost"},
		},
		{
			Unit:      "TestBackupCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
			Labels:    map[string]string{"component": "Storage Cost"},
		},
		{
			Unit:      "TestBackupCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
		},
		{
			Unit:      "TestTransfer",
			CostPrice: decimal.NewFromInt(0),
			RawPrice:  decimal.NewFromInt(2),
			Labels:    map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue},
		},
		{
			Unit:      "TestRU",
			CostPrice: decimal.NewFromInt(0),
			RawPrice:  decimal.NewFromInt(2),
			Labels:    map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue, constant.BetaCategoryLabelKey: betaCat},
		},
		{
			Unit:      "TestRU",
			CostPrice: decimal.NewFromInt(2),
			RawPrice:  decimal.NewFromInt(2),
		},
		{
			Unit:      "TestRU",
			CostPrice: decimal.NewFromInt(0),
			RawPrice:  decimal.NewFromInt(20),
			Labels:    map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue},
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:  "TestA1",
			Category: "TestA",
		},
		{
			UnitKey:  "TestA2",
			Category: "TestA",
		},
		{
			UnitKey:  "TestB1",
			Category: "TestB",
		},
		{
			UnitKey:  "TestCateGory",
			Category: "Node Compute Cost",
		},
		{
			UnitKey:  "TestTransfer",
			Category: "Data Transfer Cost - AWS Load Balancing",
		},
		{
			UnitKey:  "TestBackupCateGory",
			Category: "Backup Cost",
		},
		{
			UnitKey:  "TestRU",
			Category: "Serverless Request Unit Cost",
		},
	}
	res := costexplorer.CostSummaryFromUnitCosts(costs, units)
	//pp.Print(res)
	assert.NotNil(res)
	assert.Equal(res.CostPrice, decimal.NewFromInt(172))
	assert.Len(res.CategoryUnitsCosts, 7)
	assert.Equal(res.CategoryUnitsCosts["TestA"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.KindCategoryUnitsCost["TestA"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.CategoryUnitsCosts["TestA"].RawPrice, decimal.NewFromInt(4))
	assert.Equal(res.CategoryUnitsCosts["TestB"].CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.CategoryUnitsCosts["TestB"].RawPrice, decimal.NewFromInt(1))
	assert.Equal(res.CategoryUnitsCosts[config.Get().GetString(config.DefaultUnitCategory)].CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.CategoryUnitsCosts[config.Get().GetString(config.DefaultUnitCategory)].RawPrice, decimal.NewFromInt(2))

	assert.Equal(res.CategoryItemUnitsCosts["Data Transfer"].CostPrice, decimal.NewFromInt(111))
	assert.Equal(res.CategoryItemUnitsCosts["Data Transfer"].RawPrice, decimal.NewFromInt(224))
	assert.Equal(res.CategoryItemUnitsCosts["Data Transfer"].CategoryUnitsCosts["Load Balancing"].CostPrice, decimal.NewFromInt(111))
	assert.Equal(res.CategoryItemUnitsCosts["Data Transfer"].CategoryUnitsCosts["Load Balancing"].RawPrice, decimal.NewFromInt(222))
	assert.Equal(res.CategoryItemUnitsCosts["Data Transfer"].CategoryUnitsCosts["Load Balancing (Beta)"].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.CategoryItemUnitsCosts["Data Transfer"].CategoryUnitsCosts["Load Balancing (Beta)"].RawPrice, decimal.NewFromInt(2))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units"].CostPrice, decimal.NewFromInt(2))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units"].RawPrice, decimal.NewFromInt(2))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units (Beta)"].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units (Beta)"].RawPrice, decimal.NewFromInt(22))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[betaCat].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[betaCat].RawPrice, decimal.NewFromInt(2))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[costexplorer.BetaNoCatName].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[costexplorer.BetaNoCatName].RawPrice, decimal.NewFromInt(20))

	assert.Equal(res.KindCategoryUnitsCost["TiDB Dedicated"].CategoryItemUnitsCosts["Data Transfer"].CategoryUnitsCosts["Load Balancing (Beta)"].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.KindCategoryUnitsCost["TiDB Dedicated"].CategoryItemUnitsCosts["Data Transfer"].CategoryUnitsCosts["Load Balancing (Beta)"].RawPrice, decimal.NewFromInt(2))
	assert.Equal(res.KindCategoryUnitsCost["TiDB Serverless"].CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[betaCat].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.KindCategoryUnitsCost["TiDB Serverless"].CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[betaCat].RawPrice, decimal.NewFromInt(2))
	assert.Equal(res.KindCategoryUnitsCost["TiDB Serverless"].CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[costexplorer.BetaNoCatName].CostPrice, decimal.NewFromInt(0))
	assert.Equal(res.KindCategoryUnitsCost["TiDB Serverless"].CategoryItemUnitsCosts["Request Units (Beta)"].CategoryUnitsCosts[costexplorer.BetaNoCatName].RawPrice, decimal.NewFromInt(20))

	assert.Equal(res.LabelsUnitsCosts["project"]["test1"].CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.LabelsUnitsCosts["project"]["test1"].RawPrice, decimal.NewFromInt(2))
	assert.Equal(res.LabelsUnitsCosts["project"]["test2"].CostPrice, decimal.NewFromInt(1))
	assert.Equal(res.LabelsUnitsCosts["project"]["test2"].RawPrice, decimal.NewFromInt(2))
	assert.Len(res.LabelsUnitsCosts, 2)
	assert.Len(res.LabelsUnitsCosts["project"], 3)
	assert.Len(res.LabelsUnitsCosts[constant.MSPCustomerIDLabel], 1)
}

func (suite *CostExplorerSuite) TestBillsCostExplorer() {
	assert := suite.Assert()
	costs := []entity.CostsDetailsQuery{
		{
			BilledDate: "2022-10-01",
			Unit:       "TestA1",
			CostPrice:  decimal.NewFromFloat(1.1223002),
			RawPrice:   decimal.NewFromInt(2),
			Labels:     map[string]string{"project": "test1"},
		},
		{
			BilledDate: "2022-10-02",
			Unit:       "TestA2",
			CostPrice:  decimal.NewFromInt(1),
			RawPrice:   decimal.NewFromInt(2),
			Labels:     map[string]string{"project": "test2"},
		},
		{
			BilledDate: "2022-10-03",
			Unit:       "TestB1",
			CostPrice:  decimal.NewFromInt(1),
			RawPrice:   decimal.NewFromInt(1),
		},
		{
			BilledDate: "2022-10-04",
			Unit:       "TestNo",
			CostPrice:  decimal.NewFromInt(1),
			RawPrice:   decimal.NewFromInt(2),
		},
		{
			BilledDate: "2022-10-05",
			Unit:       "TestCateGory:gcp",
			CostPrice:  decimal.NewFromInt(11),
			RawPrice:   decimal.NewFromInt(22),
			Labels:     map[string]string{"component": "COMPONENT_TYPE_TIDB"},
		},
		{
			BilledDate: "2022-10-06",
			Unit:       "TestTransfer:aws",
			CostPrice:  decimal.NewFromInt(111),
			RawPrice:   decimal.NewFromInt(222),
		},
		{
			BilledDate: "2022-10-07",
			Unit:       "TestCateGory:gcp",
			CostPrice:  decimal.NewFromInt(11),
			RawPrice:   decimal.NewFromInt(22),
			Labels:     map[string]string{"component": "COMPONENT_TYPE_TIKV"},
		},
		{
			BilledDate: "2022-10-07",
			Unit:       "TestCateGory:gcp",
			CostPrice:  decimal.NewFromInt(111),
			RawPrice:   decimal.NewFromInt(22),
			Labels:     map[string]string{"component": "COMPONENT_TYPE_TIKV"},
		},
		{
			Unit:      "TestBackupCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
			Labels:    map[string]string{"component": "Replication Cost"},
		},
		{
			Unit:      "TestBackupCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
			Labels:    map[string]string{"component": "Storage Cost"},
		},
		{
			Unit:      "TestBackupCateGory",
			CostPrice: decimal.NewFromInt(11),
			RawPrice:  decimal.NewFromInt(22),
		},
		{
			BilledDate:     "2022-10-09",
			Unit:           "TestRU",
			Cost:           2000,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(0),
			RawPrice:       decimal.NewFromInt(2),
			Labels:         map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue, constant.BetaCategoryLabelKey: betaCat},
		},
		{
			BilledDate:     "2022-10-11",
			Unit:           "TestRU",
			Cost:           3000,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(2),
			RawPrice:       decimal.NewFromInt(2),
		},
		{
			BilledDate:     "2022-10-12",
			Unit:           "TestTransfer:aws",
			Cost:           4000,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(0),
			RawPrice:       decimal.NewFromInt(2),
			Labels:         map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue},
		},
		{
			BilledDate: "2022-10-13",
			Unit:       "TestClusterId:aws",
			CostPrice:  decimal.NewFromInt(2),
			RawPrice:   decimal.NewFromInt(2),
			Labels:     map[string]string{constant.ClusterLabelKey: "1111"},
		},
		{
			BilledDate: "2022-10-13",
			Unit:       "TestClusterId:aws",
			CostPrice:  decimal.NewFromInt(2),
			RawPrice:   decimal.NewFromInt(2),
			Labels:     map[string]string{constant.ClusterLabelKey: "1111"},
		},
		{
			BilledDate: "2022-10-13",
			Unit:       "TestClusterId:aws",
			CostPrice:  decimal.NewFromInt(2),
			RawPrice:   decimal.NewFromInt(2),
			Labels:     map[string]string{constant.ClusterLabelKey: "222"},
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:  "TestA1",
			Category: "TestA",
		},
		{
			UnitKey:  "TestA2",
			Category: "TestA",
		},
		{
			UnitKey:  "TestB1",
			Category: "TestB",
		},
		{
			UnitKey:  "TestCateGory:gcp",
			Category: "Node Compute Cost",
			Region:   "asia-northeast2",
			Provider: "GCP",
		},
		{
			UnitKey:     "TestTransfer:aws",
			Category:    "Data Transfer Cost - AWS Load Balancing",
			Region:      "asia-southeast1",
			Provider:    "AWS",
			MeasureUnit: "GB",
		},
		{
			UnitKey:  "TestBackupCateGory",
			Category: "Backup Cost",
		},
		{
			UnitKey:     "TestRU",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "UnitTableShouldBeIgnored",
			Price:       decimal.NewFromFloat(0.001),
			PriceType:   entity.PriceType_Normal,
		},
		{
			UnitKey:  "TestClusterId:aws",
			Category: "Backup",
		},
	}

	res, err := costexplorer.BillsDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	//pp.Print(res)
	assert.Len(res.Details, 13)
	assert.Equal("TiDB Serverless - Request Units (Beta) - Test Beta Category", res.Details[9].Service)
	assert.Equal("TiDB Serverless - Request Units", res.Details[10].Service)
	assert.Equal("TiDB Dedicated - Data Transfer - Load Balancing (Beta)", res.Details[11].Service)
	resExport, err1 := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err1)
	//pp.Print(resExport)
	assert.Len(resExport, 14)
	assert.Equal(resExport[6].Amount, decimal.NewFromInt(44))
	assert.Equal(resExport[9].Amount, decimal.NewFromInt(2))
	assert.Equal(resExport[9].BillableCost, decimal.NewFromInt(0))
	assert.Equal(resExport[10].Amount, decimal.NewFromInt(2))
	assert.Equal(resExport[10].BillableCost, decimal.NewFromInt(2))
	assert.Equal(resExport[12].BillableCost, decimal.NewFromInt(4))
	assert.Equal(resExport[13].BillableCost, decimal.NewFromInt(2))
	exportByService := map[string]forms.ExportUsageDetails{}
	for _, detail := range resExport {
		exportByService[detail.Service] = detail
	}
	assert.Equal(decimal.NewFromInt(2000), exportByService["TiDB Serverless - Request Units (Beta) - Test Beta Category"].Usage)
	assert.Equal("RU", exportByService["TiDB Serverless - Request Units (Beta) - Test Beta Category"].Unit)
	assert.Equal(decimal.NewFromInt(2000), exportByService["TiDB Serverless - Request Units"].Usage)
	assert.Equal("RU", exportByService["TiDB Serverless - Request Units"].Unit)
	assert.True(exportByService["TiDB Dedicated - Data Transfer - Load Balancing (Beta)"].Usage.IsZero())
	assert.Equal("", exportByService["TiDB Dedicated - Data Transfer - Load Balancing (Beta)"].Unit)

}

func (suite *CostExplorerSuite) TestBillsCostExplorerIgnoreBeta() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	u1, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitA",
		Category:  "category_A",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	u2, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_unitB",
		Category:  "category_B",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	unitCosts1 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u1.UnitKey,
		Cost:          5,
	}
	unitCosts2 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u2.UnitKey,
		Cost:          5,
	}
	unitCosts3 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u1.UnitKey,
		Cost:          5,
		Labels:        map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue},
	}
	unitCosts4 := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          u2.UnitKey,
		Cost:          5,
		Labels:        map[string]string{constant.BetaLabelKey: constant.BetaLabelValueTrue, constant.BetaCategoryLabelKey: betaCat},
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts1)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts2)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts3)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts4)
	assert.NoError(err)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.CalculateCost(tenantID, 1, &forms.CostExplorerQuery{
		StartDate:  billingDate,
		EndDate:    billingDate.Add(time.Hour),
		Units:      []string{u1.UnitKey, u2.UnitKey},
		IgnoreBeta: true,
	})
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), res.CreditsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.UnbindedInvoiceCosts.RawPrice)
	assert.Equal(decimal.NewFromInt(0), res.UnbindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.BindedInvoiceCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(0), res.InvoiceCostsCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(10), res.TotalCosts.RawPrice)
	res, err = costExplorerModel.CalculateCost(tenantID, 1, &forms.CostExplorerQuery{
		StartDate: billingDate,
		EndDate:   billingDate.Add(time.Hour),
		Units:     []string{u1.UnitKey, u2.UnitKey},
	})
	assert.NoError(err)
	assert.Equal(decimal.NewFromInt(10), res.UnbindedInvoiceCosts.RawPrice)
	assert.Equal(decimal.NewFromInt(10), res.TotalCosts.CostPrice)
	assert.Equal(decimal.NewFromInt(20), res.TotalCosts.RawPrice)
}
func (suite *CostExplorerSuite) TestCategoryView() {
	assert := suite.Assert()
	costExplorerModel := &costexplorer.CostExplorerModel{}
	categoryLabels := datainit.GetCategoryLabel()

	res := costExplorerModel.GetAllCategoryViewForOps(categoryLabels)
	//pp.Print(res)
	assert.Equal(res[0].Name, "TiDB Serverless")

}

func (suite *CostExplorerSuite) TestOpneAPI() {
	assert := suite.Assert()

	config.Get().Set(config.DefaultLabelValueCategory, "Other Charges")

	jsonData := `{
	"binded_invoice_costs": {
		"order": 0,
			"cost_price": "0",
			"raw_price": "0",
			"discount_price": "0",
			"category_unit_costs": {
		},
		"label_unit_costs": {
			"project": {
			}
		},
		"category_item_unit_costs": {
		},
		"category_use_item": true,
			"kind_category_units_costs": {
		}
	},
	"unbind_invoice_costs": {
		"order": 0,
			"cost_price": "5786.41",
			"raw_price": "6780.74",
			"discount_price": "994.33",
			"category_unit_costs": {
			"Backup Cost": {
				"order": 0,
					"cost_price": "206.56",
					"raw_price": "206.56",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Internet": {
				"order": 0,
					"cost_price": "18.04",
					"raw_price": "18.04",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Load Balancing": {
				"order": 0,
					"cost_price": "125.12",
					"raw_price": "125.12",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Same Region": {
				"order": 0,
					"cost_price": "574.51",
					"raw_price": "574.51",
					"discount_price": "0"
			},
			"Node Compute Cost": {
				"order": 0,
					"cost_price": "2829.94",
					"raw_price": "3824.27",
					"discount_price": "994.33"
			},
			"Node Storage Cost": {
				"order": 0,
					"cost_price": "945.84",
					"raw_price": "945.84",
					"discount_price": "0"
			},
			"Support Plan": {
				"order": 0,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0"
			}
		},
		"label_unit_costs": {
			"project": {
				"CertiK": {
					"order": 0,
						"cost_price": "4700.01",
						"raw_price": "5694.34",
						"discount_price": "994.33",
						"category_unit_costs": {
						"Backup Cost": {
							"order": 0,
								"cost_price": "206.56",
								"raw_price": "206.56",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Internet": {
							"order": 0,
								"cost_price": "18.04",
								"raw_price": "18.04",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Load Balancing": {
							"order": 0,
								"cost_price": "125.12",
								"raw_price": "125.12",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Same Region": {
							"order": 0,
								"cost_price": "574.51",
								"raw_price": "574.51",
								"discount_price": "0"
						},
						"Node Compute Cost": {
							"order": 0,
								"cost_price": "2829.94",
								"raw_price": "3824.27",
								"discount_price": "994.33"
						},
						"Node Storage Cost": {
							"order": 0,
								"cost_price": "945.84",
								"raw_price": "945.84",
								"discount_price": "0"
						}
					}
				},
				"Other Charges": {
					"order": 100,
						"cost_price": "1086.4",
						"raw_price": "1086.4",
						"discount_price": "0",
						"category_unit_costs": {
						"Support Plan": {
							"order": 0,
								"cost_price": "1086.4",
								"raw_price": "1086.4",
								"discount_price": "0"
						}
					}
				}
			}
		},
		"category_item_unit_costs": {
			"Backup": {
				"order": 3,
					"cost_price": "206.56",
					"raw_price": "206.56",
					"discount_price": "0",
					"category_unit_costs": {
					"Storage": {
						"order": 1,
							"cost_price": "206.56",
							"raw_price": "206.56",
							"discount_price": "0"
					}
				}
			},
			"Data Transfer": {
				"order": 6,
					"cost_price": "717.67",
					"raw_price": "717.67",
					"discount_price": "0",
					"category_unit_costs": {
					"Internet": {
						"order": 1,
							"cost_price": "18.04",
							"raw_price": "18.04",
							"discount_price": "0"
					},
					"Load Balancing": {
						"order": 4,
							"cost_price": "125.12",
							"raw_price": "125.12",
							"discount_price": "0"
					},
					"Same Region": {
						"order": 3,
							"cost_price": "574.51",
							"raw_price": "574.51",
							"discount_price": "0"
					}
				}
			},
			"Node Compute": {
				"order": 1,
					"cost_price": "2829.94",
					"raw_price": "3824.27",
					"discount_price": "994.33",
					"category_unit_costs": {
					"TiDB": {
						"order": 1,
							"cost_price": "995.51",
							"raw_price": "1345.3",
							"discount_price": "349.79"
					},
					"TiKV": {
						"order": 2,
							"cost_price": "1834.43",
							"raw_price": "2478.97",
							"discount_price": "644.54"
					}
				}
			},
			"Node Storage": {
				"order": 2,
					"cost_price": "945.84",
					"raw_price": "945.84",
					"discount_price": "0",
					"category_unit_costs": {
					"TiKV": {
						"order": 1,
							"cost_price": "945.84",
							"raw_price": "945.84",
							"discount_price": "0"
					}
				}
			},
			"Support Plan": {
				"order": 0,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0",
					"category_unit_costs": null
			}
		},
		"category_use_item": true,
			"kind_category_units_costs": {
			"Support Plan": {
				"order": 5,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0",
					"category_item_unit_costs": {
				}
			},
			"TiDB Dedicated": {
				"order": 2,
					"cost_price": "4700.01",
					"raw_price": "5694.34",
					"discount_price": "994.33",
					"category_item_unit_costs": {
					"Backup": {
						"order": 3,
							"cost_price": "206.56",
							"raw_price": "206.56",
							"discount_price": "0",
							"category_unit_costs": {
							"Storage": {
								"order": 1,
									"cost_price": "206.56",
									"raw_price": "206.56",
									"discount_price": "0"
							}
						}
					},
					"Data Transfer": {
						"order": 6,
							"cost_price": "717.67",
							"raw_price": "717.67",
							"discount_price": "0",
							"category_unit_costs": {
							"Internet": {
								"order": 1,
									"cost_price": "18.04",
									"raw_price": "18.04",
									"discount_price": "0"
							},
							"Load Balancing": {
								"order": 4,
									"cost_price": "125.12",
									"raw_price": "125.12",
									"discount_price": "0"
							},
							"Same Region": {
								"order": 3,
									"cost_price": "574.51",
									"raw_price": "574.51",
									"discount_price": "0"
							}
						}
					},
					"Node Compute": {
						"order": 1,
							"cost_price": "2829.94",
							"raw_price": "3824.27",
							"discount_price": "994.33",
							"category_unit_costs": {
							"TiDB": {
								"order": 1,
									"cost_price": "995.51",
									"raw_price": "1345.3",
									"discount_price": "349.79"
							},
							"TiKV": {
								"order": 2,
									"cost_price": "1834.43",
									"raw_price": "2478.97",
									"discount_price": "644.54"
							}
						}
					},
					"Node Storage": {
						"order": 2,
							"cost_price": "945.84",
							"raw_price": "945.84",
							"discount_price": "0",
							"category_unit_costs": {
							"TiKV": {
								"order": 1,
									"cost_price": "945.84",
									"raw_price": "945.84",
									"discount_price": "0"
							}
						}
					}
				}
			}
		}
	},
	"invoice_costs": {
		"order": 0,
			"cost_price": "5786.41",
			"raw_price": "6780.74",
			"discount_price": "994.33",
			"category_unit_costs": {
			"Backup Cost": {
				"order": 0,
					"cost_price": "206.56",
					"raw_price": "206.56",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Internet": {
				"order": 0,
					"cost_price": "18.04",
					"raw_price": "18.04",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Load Balancing": {
				"order": 0,
					"cost_price": "125.12",
					"raw_price": "125.12",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Same Region": {
				"order": 0,
					"cost_price": "574.51",
					"raw_price": "574.51",
					"discount_price": "0"
			},
			"Node Compute Cost": {
				"order": 0,
					"cost_price": "2829.94",
					"raw_price": "3824.27",
					"discount_price": "994.33"
			},
			"Node Storage Cost": {
				"order": 0,
					"cost_price": "945.84",
					"raw_price": "945.84",
					"discount_price": "0"
			},
			"Support Plan": {
				"order": 0,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0"
			}
		},
		"label_unit_costs": {
			"project": {
				"CertiK": {
					"order": 0,
						"cost_price": "4700.01",
						"raw_price": "5694.34",
						"discount_price": "994.33",
						"category_unit_costs": {
						"Backup Cost": {
							"order": 0,
								"cost_price": "206.56",
								"raw_price": "206.56",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Internet": {
							"order": 0,
								"cost_price": "18.04",
								"raw_price": "18.04",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Load Balancing": {
							"order": 0,
								"cost_price": "125.12",
								"raw_price": "125.12",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Same Region": {
							"order": 0,
								"cost_price": "574.51",
								"raw_price": "574.51",
								"discount_price": "0"
						},
						"Node Compute Cost": {
							"order": 0,
								"cost_price": "2829.94",
								"raw_price": "3824.27",
								"discount_price": "994.33"
						},
						"Node Storage Cost": {
							"order": 0,
								"cost_price": "945.84",
								"raw_price": "945.84",
								"discount_price": "0"
						}
					}
				},
				"Other Charges": {
					"order": 100,
						"cost_price": "1086.4",
						"raw_price": "1086.4",
						"discount_price": "0",
						"category_unit_costs": {
						"Support Plan": {
							"order": 0,
								"cost_price": "1086.4",
								"raw_price": "1086.4",
								"discount_price": "0"
						}
					}
				}
			}
		},
		"category_item_unit_costs": {
			"Backup": {
				"order": 3,
					"cost_price": "206.56",
					"raw_price": "206.56",
					"discount_price": "0",
					"category_unit_costs": {
					"Storage": {
						"order": 1,
							"cost_price": "206.56",
							"raw_price": "206.56",
							"discount_price": "0"
					}
				}
			},
			"Data Transfer": {
				"order": 6,
					"cost_price": "717.67",
					"raw_price": "717.67",
					"discount_price": "0",
					"category_unit_costs": {
					"Internet": {
						"order": 1,
							"cost_price": "18.04",
							"raw_price": "18.04",
							"discount_price": "0"
					},
					"Load Balancing": {
						"order": 4,
							"cost_price": "125.12",
							"raw_price": "125.12",
							"discount_price": "0"
					},
					"Same Region": {
						"order": 3,
							"cost_price": "574.51",
							"raw_price": "574.51",
							"discount_price": "0"
					}
				}
			},
			"Node Compute": {
				"order": 1,
					"cost_price": "2829.94",
					"raw_price": "3824.27",
					"discount_price": "994.33",
					"category_unit_costs": {
					"TiDB": {
						"order": 1,
							"cost_price": "995.51",
							"raw_price": "1345.3",
							"discount_price": "349.79"
					},
					"TiKV": {
						"order": 2,
							"cost_price": "1834.43",
							"raw_price": "2478.97",
							"discount_price": "644.54"
					}
				}
			},
			"Node Storage": {
				"order": 2,
					"cost_price": "945.84",
					"raw_price": "945.84",
					"discount_price": "0",
					"category_unit_costs": {
					"TiKV": {
						"order": 1,
							"cost_price": "945.84",
							"raw_price": "945.84",
							"discount_price": "0"
					}
				}
			},
			"Support Plan": {
				"order": 0,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0",
					"category_unit_costs": null
			}
		},
		"category_use_item": true,
			"kind_category_units_costs": {
			"Support Plan": {
				"order": 5,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0",
					"category_item_unit_costs": {
				}
			},
			"TiDB Dedicated": {
				"order": 2,
					"cost_price": "4700.01",
					"raw_price": "5694.34",
					"discount_price": "994.33",
					"category_item_unit_costs": {
					"Backup": {
						"order": 3,
							"cost_price": "206.56",
							"raw_price": "206.56",
							"discount_price": "0",
							"category_unit_costs": {
							"Storage": {
								"order": 1,
									"cost_price": "206.56",
									"raw_price": "206.56",
									"discount_price": "0"
							}
						}
					},
					"Data Transfer": {
						"order": 6,
							"cost_price": "717.67",
							"raw_price": "717.67",
							"discount_price": "0",
							"category_unit_costs": {
							"Internet": {
								"order": 1,
									"cost_price": "18.04",
									"raw_price": "18.04",
									"discount_price": "0"
							},
							"Load Balancing": {
								"order": 4,
									"cost_price": "125.12",
									"raw_price": "125.12",
									"discount_price": "0"
							},
							"Same Region": {
								"order": 3,
									"cost_price": "574.51",
									"raw_price": "574.51",
									"discount_price": "0"
							}
						}
					},
					"Node Compute": {
						"order": 1,
							"cost_price": "2829.94",
							"raw_price": "3824.27",
							"discount_price": "994.33",
							"category_unit_costs": {
							"TiDB": {
								"order": 1,
									"cost_price": "995.51",
									"raw_price": "1345.3",
									"discount_price": "349.79"
							},
							"TiKV": {
								"order": 2,
									"cost_price": "1834.43",
									"raw_price": "2478.97",
									"discount_price": "644.54"
							}
						}
					},
					"Node Storage": {
						"order": 2,
							"cost_price": "945.84",
							"raw_price": "945.84",
							"discount_price": "0",
							"category_unit_costs": {
							"TiKV": {
								"order": 1,
									"cost_price": "945.84",
									"raw_price": "945.84",
									"discount_price": "0"
							}
						}
					}
				}
			}
		}
	},
	"credits_costs": {
		"order": 0,
			"cost_price": "1434.64",
			"raw_price": "1754.38",
			"discount_price": "319.74",
			"category_unit_costs": {
			"Backup Cost": {
				"order": 0,
					"cost_price": "60.26",
					"raw_price": "60.26",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Internet": {
				"order": 0,
					"cost_price": "5.25",
					"raw_price": "5.25",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Load Balancing": {
				"order": 0,
					"cost_price": "20.32",
					"raw_price": "20.32",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Same Region": {
				"order": 0,
					"cost_price": "137.96",
					"raw_price": "137.96",
					"discount_price": "0"
			},
			"Node Compute Cost": {
				"order": 0,
					"cost_price": "909.96",
					"raw_price": "1229.7",
					"discount_price": "319.74"
			},
			"Node Storage Cost": {
				"order": 0,
					"cost_price": "300.89",
					"raw_price": "300.89",
					"discount_price": "0"
			}
		},
		"label_unit_costs": {
			"project": {
				"CertiK": {
					"order": 0,
						"cost_price": "1434.64",
						"raw_price": "1754.38",
						"discount_price": "319.74",
						"category_unit_costs": {
						"Backup Cost": {
							"order": 0,
								"cost_price": "60.26",
								"raw_price": "60.26",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Internet": {
							"order": 0,
								"cost_price": "5.25",
								"raw_price": "5.25",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Load Balancing": {
							"order": 0,
								"cost_price": "20.32",
								"raw_price": "20.32",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Same Region": {
							"order": 0,
								"cost_price": "137.96",
								"raw_price": "137.96",
								"discount_price": "0"
						},
						"Node Compute Cost": {
							"order": 0,
								"cost_price": "909.96",
								"raw_price": "1229.7",
								"discount_price": "319.74"
						},
						"Node Storage Cost": {
							"order": 0,
								"cost_price": "300.89",
								"raw_price": "300.89",
								"discount_price": "0"
						}
					}
				}
			}
		},
		"category_item_unit_costs": {
			"Backup": {
				"order": 3,
					"cost_price": "60.26",
					"raw_price": "60.26",
					"discount_price": "0",
					"category_unit_costs": {
					"Storage": {
						"order": 1,
							"cost_price": "60.26",
							"raw_price": "60.26",
							"discount_price": "0"
					}
				}
			},
			"Data Transfer": {
				"order": 6,
					"cost_price": "163.53",
					"raw_price": "163.53",
					"discount_price": "0",
					"category_unit_costs": {
					"Internet": {
						"order": 1,
							"cost_price": "5.25",
							"raw_price": "5.25",
							"discount_price": "0"
					},
					"Load Balancing": {
						"order": 4,
							"cost_price": "20.32",
							"raw_price": "20.32",
							"discount_price": "0"
					},
					"Same Region": {
						"order": 3,
							"cost_price": "137.96",
							"raw_price": "137.96",
							"discount_price": "0"
					}
				}
			},
			"Node Compute": {
				"order": 1,
					"cost_price": "909.96",
					"raw_price": "1229.7",
					"discount_price": "319.74",
					"category_unit_costs": {
					"TiDB": {
						"order": 1,
							"cost_price": "332.35",
							"raw_price": "449.13",
							"discount_price": "116.78"
					},
					"TiKV": {
						"order": 2,
							"cost_price": "577.61",
							"raw_price": "780.57",
							"discount_price": "202.96"
					}
				}
			},
			"Node Storage": {
				"order": 2,
					"cost_price": "300.89",
					"raw_price": "300.89",
					"discount_price": "0",
					"category_unit_costs": {
					"TiKV": {
						"order": 1,
							"cost_price": "300.89",
							"raw_price": "300.89",
							"discount_price": "0"
					}
				}
			}
		},
		"category_use_item": true,
			"kind_category_units_costs": {
			"TiDB Dedicated": {
				"order": 2,
					"cost_price": "1434.64",
					"raw_price": "1754.38",
					"discount_price": "319.74",
					"category_item_unit_costs": {
					"Backup": {
						"order": 3,
							"cost_price": "60.26",
							"raw_price": "60.26",
							"discount_price": "0",
							"category_unit_costs": {
							"Storage": {
								"order": 1,
									"cost_price": "60.26",
									"raw_price": "60.26",
									"discount_price": "0"
							}
						}
					},
					"Data Transfer": {
						"order": 6,
							"cost_price": "163.53",
							"raw_price": "163.53",
							"discount_price": "0",
							"category_unit_costs": {
							"Internet": {
								"order": 1,
									"cost_price": "5.25",
									"raw_price": "5.25",
									"discount_price": "0"
							},
							"Load Balancing": {
								"order": 4,
									"cost_price": "20.32",
									"raw_price": "20.32",
									"discount_price": "0"
							},
							"Same Region": {
								"order": 3,
									"cost_price": "137.96",
									"raw_price": "137.96",
									"discount_price": "0"
							}
						}
					},
					"Node Compute": {
						"order": 1,
							"cost_price": "909.96",
							"raw_price": "1229.7",
							"discount_price": "319.74",
							"category_unit_costs": {
							"TiDB": {
								"order": 1,
									"cost_price": "332.35",
									"raw_price": "449.13",
									"discount_price": "116.78"
							},
							"TiKV": {
								"order": 2,
									"cost_price": "577.61",
									"raw_price": "780.57",
									"discount_price": "202.96"
							}
						}
					},
					"Node Storage": {
						"order": 2,
							"cost_price": "300.89",
							"raw_price": "300.89",
							"discount_price": "0",
							"category_unit_costs": {
							"TiKV": {
								"order": 1,
									"cost_price": "300.89",
									"raw_price": "300.89",
									"discount_price": "0"
							}
						}
					}
				}
			}
		}
	},
	"total_costs": {
		"order": 0,
			"cost_price": "7221.05",
			"raw_price": "8535.12",
			"discount_price": "1314.07",
			"category_unit_costs": {
			"Backup Cost": {
				"order": 0,
					"cost_price": "266.82",
					"raw_price": "266.82",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Internet": {
				"order": 0,
					"cost_price": "23.29",
					"raw_price": "23.29",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Load Balancing": {
				"order": 0,
					"cost_price": "145.44",
					"raw_price": "145.44",
					"discount_price": "0"
			},
			"Data Transfer Cost - AWS Same Region": {
				"order": 0,
					"cost_price": "712.47",
					"raw_price": "712.47",
					"discount_price": "0"
			},
			"Node Compute Cost": {
				"order": 0,
					"cost_price": "3739.9",
					"raw_price": "5053.97",
					"discount_price": "1314.07"
			},
			"Node Storage Cost": {
				"order": 0,
					"cost_price": "1246.73",
					"raw_price": "1246.73",
					"discount_price": "0"
			},
			"Support Plan": {
				"order": 0,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0"
			}
		},
		"label_unit_costs": {
			"project": {
				"CertiK": {
					"order": 0,
						"cost_price": "6134.65",
						"raw_price": "7448.72",
						"discount_price": "1314.07",
						"category_unit_costs": {
						"Backup Cost": {
							"order": 0,
								"cost_price": "266.82",
								"raw_price": "266.82",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Internet": {
							"order": 0,
								"cost_price": "23.29",
								"raw_price": "23.29",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Load Balancing": {
							"order": 0,
								"cost_price": "145.44",
								"raw_price": "145.44",
								"discount_price": "0"
						},
						"Data Transfer Cost - AWS Same Region": {
							"order": 0,
								"cost_price": "712.47",
								"raw_price": "712.47",
								"discount_price": "0"
						},
						"Node Compute Cost": {
							"order": 0,
								"cost_price": "3739.9",
								"raw_price": "5053.97",
								"discount_price": "1314.07"
						},
						"Node Storage Cost": {
							"order": 0,
								"cost_price": "1246.73",
								"raw_price": "1246.73",
								"discount_price": "0"
						}
					}
				},
				"Other Charges": {
					"order": 100,
						"cost_price": "1086.4",
						"raw_price": "1086.4",
						"discount_price": "0",
						"category_unit_costs": {
						"Support Plan": {
							"order": 0,
								"cost_price": "1086.4",
								"raw_price": "1086.4",
								"discount_price": "0"
						}
					}
				}
			}
		},
		"category_item_unit_costs": {
			"Backup": {
				"order": 3,
					"cost_price": "266.82",
					"raw_price": "266.82",
					"discount_price": "0",
					"category_unit_costs": {
					"Storage": {
						"order": 1,
							"cost_price": "266.82",
							"raw_price": "266.82",
							"discount_price": "0"
					}
				}
			},
			"Data Transfer": {
				"order": 6,
					"cost_price": "881.2",
					"raw_price": "881.2",
					"discount_price": "0",
					"category_unit_costs": {
					"Internet": {
						"order": 1,
							"cost_price": "23.29",
							"raw_price": "23.29",
							"discount_price": "0"
					},
					"Load Balancing": {
						"order": 4,
							"cost_price": "145.44",
							"raw_price": "145.44",
							"discount_price": "0"
					},
					"Same Region": {
						"order": 3,
							"cost_price": "712.47",
							"raw_price": "712.47",
							"discount_price": "0"
					}
				}
			},
			"Node Compute": {
				"order": 1,
					"cost_price": "3739.9",
					"raw_price": "5053.97",
					"discount_price": "1314.07",
					"category_unit_costs": {
					"TiDB": {
						"order": 1,
							"cost_price": "1327.86",
							"raw_price": "1794.43",
							"discount_price": "466.57"
					},
					"TiKV": {
						"order": 2,
							"cost_price": "2412.04",
							"raw_price": "3259.54",
							"discount_price": "847.5"
					}
				}
			},
			"Node Storage": {
				"order": 2,
					"cost_price": "1246.73",
					"raw_price": "1246.73",
					"discount_price": "0",
					"category_unit_costs": {
					"TiKV": {
						"order": 1,
							"cost_price": "1246.73",
							"raw_price": "1246.73",
							"discount_price": "0"
					}
				}
			},
			"Support Plan": {
				"order": 0,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0",
					"category_unit_costs": null
			}
		},
		"category_use_item": true,
			"kind_category_units_costs": {
			"Support Plan": {
				"order": 5,
					"cost_price": "1086.4",
					"raw_price": "1086.4",
					"discount_price": "0",
					"category_item_unit_costs": {
				}
			},
			"TiDB Dedicated": {
				"order": 2,
					"cost_price": "6134.65",
					"raw_price": "7448.72",
					"discount_price": "1314.07",
					"category_item_unit_costs": {
					"Backup": {
						"order": 3,
							"cost_price": "266.82",
							"raw_price": "266.82",
							"discount_price": "0",
							"category_unit_costs": {
							"Storage": {
								"order": 1,
									"cost_price": "266.82",
									"raw_price": "266.82",
									"discount_price": "0"
							}
						}
					},
					"Data Transfer": {
						"order": 6,
							"cost_price": "881.2",
							"raw_price": "881.2",
							"discount_price": "0",
							"category_unit_costs": {
							"Internet": {
								"order": 1,
									"cost_price": "23.29",
									"raw_price": "23.29",
									"discount_price": "0"
							},
							"Load Balancing": {
								"order": 4,
									"cost_price": "145.44",
									"raw_price": "145.44",
									"discount_price": "0"
							},
							"Same Region": {
								"order": 3,
									"cost_price": "712.47",
									"raw_price": "712.47",
									"discount_price": "0"
							}
						}
					},
					"Node Compute": {
						"order": 1,
							"cost_price": "3739.9",
							"raw_price": "5053.97",
							"discount_price": "1314.07",
							"category_unit_costs": {
							"TiDB": {
								"order": 1,
									"cost_price": "1327.86",
									"raw_price": "1794.43",
									"discount_price": "466.57"
							},
							"TiKV": {
								"order": 2,
									"cost_price": "2412.04",
									"raw_price": "3259.54",
									"discount_price": "847.5"
							}
						}
					},
					"Node Storage": {
						"order": 2,
							"cost_price": "1246.73",
							"raw_price": "1246.73",
							"discount_price": "0",
							"category_unit_costs": {
							"TiKV": {
								"order": 1,
									"cost_price": "1246.73",
									"raw_price": "1246.73",
									"discount_price": "0"
							}
						}
					}
				}
			}
		}
	},
	"invoice_generated": true
}`
	var costs forms.CostExplorerResult
	err := json.Unmarshal([]byte(jsonData), &costs)
	if err != nil {
		fmt.Println("s:", err)
		return
	}
	billsServiceCosts, overviewBillsCosts := (&costexplorer.CostExplorerModel{}).GetBillsServiceCostsfromCosts(&costs)
	billsExplorer := &forms.BillsExplorer{
		BillsOverView: forms.BillsOverview{
			//BilledMonth: billedMonth.Format("2006-01"),
			BillsCosts: overviewBillsCosts,
		},
		BillsServiceCosts: billsServiceCosts,
		SummaryByProject:  (&costexplorer.CostExplorerModel{}).GetSummaryByProjectFromCosts(&costs),
	}
	runningTotal, err := decimal.NewFromString("8535.12")
	assert.Equal(runningTotal, billsExplorer.BillsOverView.RunningTotal)

}

// TestCostsSummaryFromEntities verifies the entity-to-form conversion.
func (suite *CostExplorerSuite) TestCostsSummaryFromEntities() {
	assert := suite.Assert()

	entities := []entity.UnitCostSummary{
		{
			Unit:           "unit_a",
			CostPrice:      decimal.NewFromInt(10),
			RawPrice:       decimal.NewFromInt(15),
			ConsumedSource: entity.ConsumedSource_Invoice,
			InvoiceID:      1,
			Cost:           3,
			Internal:       false,
			TenantID:       1,
			Labels:         map[string]string{"project": "p1"},
		},
		{
			Unit:           "unit_b",
			CostPrice:      decimal.NewFromInt(20),
			RawPrice:       decimal.NewFromInt(25),
			ConsumedSource: entity.ConsumedSource_Credits,
			Internal:       true,
			TenantID:       2,
		},
	}
	summaries := costexplorer.CostsSummaryFromEntities(entities)
	assert.Len(summaries, 2)
	assert.Equal(decimal.NewFromInt(10), summaries[0].CostPrice)
	assert.Equal(decimal.NewFromInt(15), summaries[0].RawPrice)
	assert.Equal("unit_a", summaries[0].Unit)
	assert.Equal("p1", summaries[0].Labels["project"])
	assert.Equal(decimal.NewFromInt(20), summaries[1].CostPrice)
	assert.True(summaries[1].Internal)
}

// TestCostFromEntity verifies the full entity-to-Costs pipeline.
func (suite *CostExplorerSuite) TestCostFromEntity() {
	assert := suite.Assert()

	entities := []entity.UnitCostSummary{
		{
			Unit:           "TestA1",
			CostPrice:      decimal.NewFromInt(1),
			RawPrice:       decimal.NewFromInt(2),
			ConsumedSource: entity.ConsumedSource_Invoice,
			Labels:         map[string]string{"project": "test1"},
		},
		{
			Unit:           "TestA2",
			CostPrice:      decimal.NewFromInt(1),
			RawPrice:       decimal.NewFromInt(2),
			ConsumedSource: entity.ConsumedSource_Invoice,
			Labels:         map[string]string{"project": "test2"},
		},
	}
	units := []forms.UnitInfo{
		{UnitKey: "TestA1", Category: "TestA"},
		{UnitKey: "TestA2", Category: "TestA"},
	}
	costs := costexplorer.CostFromEntity(entities, units)
	assert.NotNil(costs)
	assert.Equal(decimal.NewFromInt(2), costs.CostPrice)
	assert.Equal(decimal.NewFromInt(4), costs.RawPrice)
	// Both units share category "TestA", so only one category entry is created
	assert.Len(costs.CategoryUnitsCosts, 1)
	assert.Equal(decimal.NewFromInt(2), costs.CategoryUnitsCosts["TestA"].CostPrice)
}

// TestGetServiceNameEdgeCases covers the service name resolution edge cases.
func (suite *CostExplorerSuite) TestGetServiceNameEdgeCases() {
	assert := suite.Assert()
	categoryLabels := datainit.GetCategoryLabel()

	// Test with category not in labels (falls back to TrimSuffix)
	detail := entity.CostsDetailsQuery{
		Unit:   "some_unit",
		Labels: map[string]string{},
	}
	name, ok := costexplorer.GetServiceName(detail, "UnknownCategory")
	assert.True(ok)
	assert.Equal("UnknownCategory", name)

	// Test with known category that uses UseCateGory=true
	nodeComputeLabels, hasNodeCompute := categoryLabels["Node Compute Cost"]
	if hasNodeCompute {
		detail2 := entity.CostsDetailsQuery{
			Unit:   "test_unit",
			Labels: map[string]string{"component": "COMPONENT_TYPE_TIDB"},
		}
		name2, ok2 := costexplorer.GetServiceName(detail2, "Node Compute Cost")
		assert.True(ok2)
		assert.Contains(name2, nodeComputeLabels.ShowKindName)
	}
}

// TestGetServiceNameWithCategory labels covers category label-based service resolution.
func (suite *CostExplorerSuite) TestGetServiceNameCategoryLabelBased() {
	assert := suite.Assert()
	categoryLabels := datainit.GetCategoryLabel()

	// Test Data Transfer Cost with component label
	dtLabels, hasDT := categoryLabels["Data Transfer Cost - AWS Load Balancing"]
	if hasDT {
		detail := entity.CostsDetailsQuery{
			Unit:   "test_transfer",
			Labels: map[string]string{"LoadBalancing": "ALB"},
		}
		name, ok := costexplorer.GetServiceName(detail, "Data Transfer Cost - AWS Load Balancing")
		assert.True(ok)
		assert.NotEmpty(name)
		assert.NotEqual("Data Transfer", name)
		_ = dtLabels
	}
}

// TestBillsDetailsFromUnitCostsEdgeCases tests edge cases in bills details.
func (suite *CostExplorerSuite) TestBillsDetailsFromUnitCostsEdgeCases() {
	assert := suite.Assert()

	unitKey := "bills-edge-" + strings.ReplaceAll(uuid.NewString(), "-", "")
	suite.createStreamTestUnit(unitKey)

	costs := []entity.CostsDetailsQuery{
		{
			BilledDate: "2022-10-01",
			Unit:       unitKey,
			CostPrice:  decimal.NewFromInt(1),
			RawPrice:   decimal.NewFromInt(2),
			Labels:     map[string]string{"project": "test1"},
		},
	}
	units := []forms.UnitInfo{
		{UnitKey: unitKey, Category: streamTestCategory},
	}
	res, err := costexplorer.BillsDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res.Details, 1)
	// streamTestCategory is not registered as a category item, so the service name
	// is derived from TrimSuffix(category, "Cost") which yields streamTestService.
	assert.Equal(streamTestService, res.Details[0].Service)
}

// TestGetAllCategoryViewForOpsNil tests GetAllCategoryViewForOps with nil parameter.
func (suite *CostExplorerSuite) TestGetAllCategoryViewForOpsNil() {
	assert := suite.Assert()
	costExplorerModel := &costexplorer.CostExplorerModel{}

	// With nil shows, should fetch from DB cache
	res := costExplorerModel.GetAllCategoryViewForOps(nil)
	assert.NotEmpty(res)

	// Verify tree structure is built correctly
	foundDedicated := false
	foundServerless := false
	for _, root := range res {
		if root.Name == "TiDB Dedicated" {
			foundDedicated = true
		}
		if root.Name == "TiDB Serverless" {
			foundServerless = true
		}
	}
	assert.True(foundDedicated || foundServerless, "Expected TiDB Dedicated or TiDB Serverless root nodes")
}

// TestCalculateOpenAPIMonthlyCostWithCache tests the OpenAPI monthly cost wrapper.
func (suite *CostExplorerSuite) TestCalculateOpenAPIMonthlyCostWithCache() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	_, err := datainit.CreateInvoice(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 2, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(2),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

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

	costExplorerModel := &costexplorer.CostExplorerModel{}
	billsExplorer, err := costExplorerModel.CalculateOpenAPIMonthlyCostWithCache(tenantID, 1, billingDate)
	assert.NoError(err)
	assert.NotNil(billsExplorer)
	assert.NotEmpty(billsExplorer.BillsServiceCosts)
	assert.Equal(billingDate.Format("2006-01"), billsExplorer.BillsOverView.BilledMonth)
}

// TestGetTransferCostsByInvoiceId tests streaming transfer costs.
func (suite *CostExplorerSuite) TestGetTransferCostsByInvoiceId() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_transfer_unit",
		Category:  "Data Transfer Cost - AWS Load Balancing",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	assert.NoError(err)

	invoice := &entity.Invoice{
		TenantID:              tenantID,
		InvoiceDate:           billingDate,
		Status:                entity.InvoiceStatus_SENT,
		CostPrice:             decimal.NewFromInt(10),
		BindedBillingConfigID: billingConfigID,
	}
	assert.NoError(invoice.Create(db.GetDB()))

	costEntity := &entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		CostTimeStamp:         billingDate.Unix(),
		Unit:                  "test_transfer_unit",
		Cost:                  1,
		CostPrice:             decimal.NewFromInt(5),
		RawPrice:              decimal.NewFromInt(5),
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
		InvoiceID:             int(invoice.ID),
	}
	assert.NoError(costEntity.Create(db.GetDB()))

	costExplorerModel := &costexplorer.CostExplorerModel{}
	transferCosts, err := costExplorerModel.GetTransferCostsByInvoiceId(tenantID, invoice.ID)
	assert.NoError(err)
	assert.NotEmpty(transferCosts)

	// Also test ForEach variant
	var count int
	err = costExplorerModel.ForEachTransferCostByInvoiceId(tenantID, invoice.ID, func(cost entity.DateUnitCostSummary) error {
		count++
		return nil
	})
	assert.NoError(err)
	assert.Equal(len(transferCosts), count)
}

// TestCalculateOpenAPIMonthlyClusterCost tests the OpenAPI cluster monthly cost wrapper.
func (suite *CostExplorerSuite) TestCalculateOpenAPIMonthlyClusterCost() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1
	clusterID := uint(1)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_cluster_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(2),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_cluster_unit",
		Cost:          1,
		Labels:        map[string]string{constant.ClusterLabelKey: fmt.Sprint(clusterID)},
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	billsExplorer, err := costExplorerModel.CalculateOpenAPIMonthlyClusterCost(tenantID, 1, billingDate, clusterID)
	assert.NoError(err)
	assert.NotNil(billsExplorer)
	assert.NotEmpty(billsExplorer.BillsServiceCosts)
}

// TestGetMonthlyCostNoClusterName tests GetMonthlyCostNoClusterName.
func (suite *CostExplorerSuite) TestGetMonthlyCostNoClusterName() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	const tenantID uint = 1

	datainit.CreateDefaultUserUnitCosts(tenantID, billingDate, 5)
	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetMonthlyCostNoClusterName(tenantID, 0, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.NotNil(res)
	assert.True(res.TotalCosts.CostPrice.IsPositive() || res.TotalCosts.CostPrice.IsZero())
}

// TestGetDailyCostsByDateRange tests the non-excluding daily cost retrieval.
func (suite *CostExplorerSuite) TestGetDailyCostsByDateRange() {
	assert := suite.Assert()

	const tenantID uint = 1
	billingConfig, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfig(tenantID)
	assert.NoError(err)

	day0 := datainit.BillingDate
	costEntity := &entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfig.BillingConfigID,
		CostTimeStamp:         day0.Unix(),
		Unit:                  datainit.AWSNodeUnit,
		Cost:                  1,
		CostPrice:             decimal.NewFromInt(3),
		RawPrice:              decimal.NewFromInt(3),
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
	}
	assert.NoError(costEntity.Create(db.GetDB()))

	costExplorerModel := &costexplorer.CostExplorerModel{}
	costs, err := costExplorerModel.GetDailyCostsByDateRange(tenantID, billingConfig.BillingConfigID, day0, day0.AddDate(0, 0, 1))
	assert.NoError(err)
	assert.NotEmpty(costs)
}

// TestGetMonthlySpendLimitCostEmpty ensures empty period returns zeroed summary.
func (suite *CostExplorerSuite) TestGetMonthlySpendLimitCostEmpty() {
	assert := suite.Assert()

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// Far future date with no data
	res, err := costExplorerModel.GetMonthlySpendLimitCost(1, 0, 2030, 1)
	assert.NoError(err)
	assert.True(res.TotalRawPrice.IsZero())
	assert.True(res.TotalCostPrice.IsZero())
	assert.True(res.InvoiceCostPrice.IsZero())
	assert.True(res.CreditsCostPrice.IsZero())
}

// TestSpendLimitCostWithInvoiceIDs exercises the invoice IDs filter path.
func (suite *CostExplorerSuite) TestSpendLimitCostWithInvoiceIDs() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_sp_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	assert.NoError(err)

	invoice := &entity.Invoice{
		TenantID:              tenantID,
		InvoiceDate:           billingDate,
		Status:                entity.InvoiceStatus_SENT,
		CostPrice:             decimal.NewFromInt(10),
		BindedBillingConfigID: billingConfigID,
	}
	assert.NoError(invoice.Create(db.GetDB()))

	costEntity := &entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: billingConfigID,
		CostTimeStamp:         billingDate.Unix(),
		Unit:                  "test_sp_unit",
		Cost:                  1,
		CostPrice:             decimal.NewFromInt(5),
		RawPrice:              decimal.NewFromInt(5),
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
		InvoiceID:             int(invoice.ID),
	}
	assert.NoError(costEntity.Create(db.GetDB()))

	costExplorerModel := &costexplorer.CostExplorerModel{}

	// Test with invoice IDs
	queryWithIDs := &forms.CostExplorerQueryV2{
		StartDate:  billingDate,
		EndDate:    billingDate.AddDate(0, 0, 1),
		InvoiceIDs: []uint{invoice.ID},
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{billingConfigID}, queryWithIDs)
	assert.NoError(err)
	assert.NotNil(res)
}

// TestSpendLimitCostIgnoreInvoiceId exercises the IgnoreInvoiceId path.
func (suite *CostExplorerSuite) TestSpendLimitCostIgnoreInvoiceId() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_ignore_inv_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)
	measureModel := &billing.MeasureModel{}

	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_ignore_inv_unit",
		Cost:          1,
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate:       billingDate,
		EndDate:         billingDate.AddDate(0, 0, 1),
		IgnoreInvoiceId: true,
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
	assert.NoError(err)
	assert.NotNil(res)
}

// TestSpendLimitCostUseMainDB exercises the UseMainDB query path.
func (suite *CostExplorerSuite) TestSpendLimitCostUseMainDB() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_maindb_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)
	measureModel := &billing.MeasureModel{}

	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_maindb_unit",
		Cost:          1,
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.AddDate(0, 0, 1),
		UseMainDB: true,
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
	assert.NoError(err)
	assert.NotNil(res)
}

// TestBillsDetailsForDate tests the S3 bills details for a specific date.
func (suite *CostExplorerSuite) TestBillsDetailsForDate() {
	assert := suite.Assert()

	const tenantID uint = 1
	unit := "bills-date-" + strings.ReplaceAll(uuid.NewString(), "-", "")
	suite.createStreamTestUnit(unit)
	suite.seedDetailDataset(tenantID, unit)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetBillsDetailsForDate(tenantID, 0, datainit.BillingDate)
	assert.NoError(err)
	assert.NotNil(res)
}

// TestCustomerMonthlySpendLimitCostEmpty tests customer spend limit with no matching data.
func (suite *CostExplorerSuite) TestCustomerMonthlySpendLimitCostEmpty() {
	assert := suite.Assert()

	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetCustomerMonthlySpendLimitCost(1, 0, 2030, 1, 99999)
	assert.NoError(err)
	assert.True(res.TotalRawPrice.IsZero())
	assert.True(res.TotalCostPrice.IsZero())
}

// TestBillsExportDetailsFromUnitCostsEdgeCases tests edge cases in export details.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsEdgeCases() {
	assert := suite.Assert()

	// Test with empty costs
	costs := []entity.CostsDetailsQuery{}
	units := []forms.UnitInfo{
		{UnitKey: "test_unit", Category: "Node Compute Cost"},
	}
	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Empty(res)

	// Test with unknown unit (falls back to default category)
	costs2 := []entity.CostsDetailsQuery{
		{
			Unit:      "unknown_unit",
			CostPrice: decimal.NewFromInt(5),
			RawPrice:  decimal.NewFromInt(5),
			Labels:    map[string]string{},
		},
	}
	res2, err := costexplorer.BillsExportDetailsFromUnitCosts(costs2, units)
	assert.NoError(err)
	assert.NotNil(res2)
}

// TestCalculatePartnerOpenAPIMonthlyCostWithCache tests the partner API wrapper.
func (suite *CostExplorerSuite) TestCalculatePartnerOpenAPIMonthlyCostWithCache() {
	assert := suite.Assert()

	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)
	measureModel := &billing.MeasureModel{}
	const tenantID uint = 1

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_partner_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_partner_unit",
		Cost:          1,
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// Test with tenantIDAsCustomer=false
	res, err := costExplorerModel.CalculatePartnerOpenAPIMonthlyCostWithCache([]uint{tenantID}, nil, billingDate, false)
	assert.NoError(err)
	assert.NotNil(res)

	// Test with tenantIDAsCustomer=true
	res2, err := costExplorerModel.CalculatePartnerOpenAPIMonthlyCostWithCache([]uint{tenantID}, nil, billingDate, true)
	assert.NoError(err)
	assert.NotNil(res2)
}

// TestCalculatePartnerOpenAPIMonthlyCostWithCacheZeroCosts tests when costs are zero.
func (suite *CostExplorerSuite) TestCalculatePartnerOpenAPIMonthlyCostWithCacheZeroCosts() {
	assert := suite.Assert()

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// No data at all - should still work gracefully
	res, err := costExplorerModel.CalculatePartnerOpenAPIMonthlyCostWithCache([]uint{1}, nil, time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC), false)
	assert.NoError(err)
	assert.NotNil(res)
}

// TestSpendLimitCostTenantClusterSplit tests the tenant/cluster split path in spend limit query.
func (suite *CostExplorerSuite) TestSpendLimitCostTenantClusterSplit() {
	assert := suite.Assert()

	// Create data with multiple tenants and clusters
	tests.MockCentralHandler()
	const tenantID1 uint = 1
	const tenantID2 uint = 2
	_, err := datainit.CreateBillingConfig(tenantID2, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)

	datainit.CreateDefaultUserUnitCosts(tenantID1, datainit.BillingDate, 5)
	datainit.CreateDefaultUserUnitCosts(tenantID2, datainit.BillingDate, 5)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate: datainit.BillingDate,
		EndDate:   datainit.BillingDate.AddDate(0, 0, 5),
	}
	// Multi-tenant query exercises shouldSplitCostQueryByTenantCluster path
	res, err := costExplorerModel.CalculateCostV2MultiTenants([]uint{tenantID1, tenantID2}, nil, query)
	assert.NoError(err)
	assert.NotNil(res)

	// Also test the corresponding spend limit
	year, month := datainit.BillingDate.Year(), int(datainit.BillingDate.Month())
	spendRes, err := costExplorerModel.GetMonthlySpendLimitCost(tenantID1, 0, year, month)
	assert.NoError(err)
	assert.True(spendRes.TotalRawPrice.IsPositive())
}

// TestQueryCostStreamingEdgeCases exercises uncovered paths in queryCostStreaming.
func (suite *CostExplorerSuite) TestQueryCostStreamingEdgeCases() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_stream_all_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	measureModel := &billing.MeasureModel{}
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_stream_all_unit",
		Cost:          1,
		Labels:        map[string]string{constant.ClusterLabelKey: "1"},
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}

	// Exercise cluster filter
	queryWithCluster := &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.AddDate(0, 0, 1),
		Labels:    map[string]string{constant.ClusterLabelKey: "1"},
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, queryWithCluster)
	assert.NoError(err)
	assert.NotNil(res)

	// Exercise IgnoreBeta
	queryIgnoreBeta := &forms.CostExplorerQueryV2{
		StartDate:  billingDate,
		EndDate:    billingDate.AddDate(0, 0, 1),
		IgnoreBeta: true,
	}
	res2, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, queryIgnoreBeta)
	assert.NoError(err)
	assert.NotNil(res2)

	// Exercise UseMainDB
	queryMainDB := &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.AddDate(0, 0, 1),
		UseMainDB: true,
	}
	res4, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, queryMainDB)
	assert.NoError(err)
	assert.NotNil(res4)
}

// TestSpendLimitCostBigCustomerPath exercises the big customer timeout path.
func (suite *CostExplorerSuite) TestSpendLimitCostBigCustomerPath() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_bigcust_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	measureModel := &billing.MeasureModel{}
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_bigcust_unit",
		Cost:          1,
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	// Set big customer timeout to exercise that path

	costExplorerModel := &costexplorer.CostExplorerModel{}
	query := &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.AddDate(0, 0, 1),
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
	assert.NoError(err)
	assert.NotNil(res)
}

// TestGetMonthlySpendLimitCostWithBigCustomer tests spend limit with big customer timeout disabled labels.
func (suite *CostExplorerSuite) TestGetMonthlySpendLimitCostWithBigCustomer() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2021, 11, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_bigcust_sp_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	measureModel := &billing.MeasureModel{}
	unitCosts := forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_bigcust_sp_unit",
		Cost:          1,
	}
	_, err = measureModel.CostUnits(tenantID, &unitCosts)
	assert.NoError(err)

	// Set disabled tenants list to trigger shouldRemoveLabelsFromGroupBy
	config.Get().Set(config.DisableClusterDisplayTenants, []string{fmt.Sprint(tenantID)})
	defer func() {
		config.Get().Set(config.DisableClusterDisplayTenants, []string{})
	}()

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// Use a date after 2026-02-01 to trigger shouldRemoveLabelsFromGroupBy
	res, err := costExplorerModel.GetMonthlySpendLimitCost(tenantID, 0, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.NotNil(res)
}

// TestGetMonthlySpendLimitCostAfterCutoff tests spend limit with label removal for dates after cutoff.
func (suite *CostExplorerSuite) TestGetMonthlySpendLimitCostAfterCutoff() {
	assert := suite.Assert()

	datainit.CreateDefaultUserUnitCosts(1, datainit.BillingDate, 12)
	costExplorerModel := &costexplorer.CostExplorerModel{}

	// Use a date well past the 2026-02-01 cutoff to avoid label-based group by
	futureDate := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
	res, err := costExplorerModel.GetMonthlySpendLimitCost(1, 0, futureDate.Year(), int(futureDate.Month()))
	assert.NoError(err)
	// Even with no data for future date, should return zeroed result
	assert.True(res.TotalRawPrice.IsZero())
	assert.True(res.TotalCostPrice.IsZero())
}

// TestDisableClusterDisplayTenantsSpendLimit tests spend limit with disabled cluster display tenants.
func (suite *CostExplorerSuite) TestDisableClusterDisplayTenantsSpendLimit() {
	assert := suite.Assert()

	const tenantID uint = 1
	var billingDate = time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_disabled_cluster_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	costEntity := &entity.UserUnitCost{
		CostID:                uuid.NewString(),
		TenantID:              tenantID,
		BindedBillingConfigID: 1,
		CostTimeStamp:         billingDate.Unix(),
		Unit:                  "test_disabled_cluster_unit",
		Cost:                  1,
		CostPrice:             decimal.NewFromInt(5),
		RawPrice:              decimal.NewFromInt(5),
		ConsumedSource:        entity.ConsumedSource_Invoice,
		Status:                entity.UserUnitCostStatus_DONE,
	}
	assert.NoError(costEntity.Create(db.GetDB()))

	// Set disabled tenants to trigger shouldRemoveLabelsFromGroupBy
	config.Get().Set(config.DisableClusterDisplayTenants, []string{fmt.Sprint(tenantID)})
	defer config.Get().Set(config.DisableClusterDisplayTenants, []string{})

	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetMonthlySpendLimitCost(tenantID, 0, billingDate.Year(), int(billingDate.Month()))
	assert.NoError(err)
	assert.NotNil(res)
}

// TestGetBillsDetailsForDateStarterBatched tests the batch starter for bills details.
func (suite *CostExplorerSuite) TestGetBillsDetailsForDateStarterBatched() {
	assert := suite.Assert()

	costExplorerModel := &costexplorer.CostExplorerModel{}
	res, err := costExplorerModel.GetBillsDetailsForDateStarterBatched(1, 0, datainit.BillingDate)
	assert.NoError(err)
	// Returns nil bytes when no data exists, which is valid
	_ = res
}

// TestDaySplittingQueryCostStreaming verifies that queryCostStreaming splits a multi-day
// range into per-day sub-queries and the aggregation result matches the sum of single-day queries.
func (suite *CostExplorerSuite) TestDaySplittingQueryCostStreaming() {
	assert := suite.Assert()

	const tenantID uint = 1
	billingDate := time.Date(2021, 11, 10, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_daysplit_cost_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	// Insert data on 3 consecutive days
	measureModel := &billing.MeasureModel{}
	for i := 0; i < 3; i++ {
		ts := billingDate.AddDate(0, 0, i).Unix()
		_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
			CostTimeStamp: ts,
			Unit:          "test_daysplit_cost_unit",
			Cost:          1,
		})
		assert.NoError(err)
	}

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// Multi-day query spanning 3 days — exercises day splitting
	query := &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.AddDate(0, 0, 3),
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
	assert.NoError(err)
	assert.NotNil(res)
	// Multi-day query should aggregate data from all 3 days
	assert.True(res.ExternalCosts.TotalCosts.CostPrice.GreaterThan(decimal.Zero),
		"multi-day query should aggregate all days, got total cost %s", res.ExternalCosts.TotalCosts.CostPrice)
}

// TestDaySplittingSpendLimitCost verifies that forEachSpendLimitCostRow splits a multi-day
// range and the spend-limit summary aggregates correctly.
func (suite *CostExplorerSuite) TestDaySplittingSpendLimitCost() {
	assert := suite.Assert()

	const tenantID uint = 1
	billingDate := time.Date(2021, 11, 10, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_daysplit_sl_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	// Insert data on 2 consecutive days
	measureModel := &billing.MeasureModel{}
	for i := 0; i < 2; i++ {
		ts := billingDate.AddDate(0, 0, i).Unix()
		_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
			CostTimeStamp: ts,
			Unit:          "test_daysplit_sl_unit",
			Cost:          1,
		})
		assert.NoError(err)
	}

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// Monthly query — exercises day splitting in forEachSpendLimitCostRow (full month → ~30 per-day sub-queries)
	res, err := costExplorerModel.GetMonthlySpendLimitCost(tenantID, 0, int(billingDate.Year()), int(billingDate.Month()))
	assert.NoError(err)
	assert.NotNil(res)
	assert.True(res.TotalCostPrice.GreaterThan(decimal.Zero),
		"multi-day spend limit should aggregate all days, got total %s", res.TotalCostPrice)
}

// TestDaySplittingBillsDetails verifies that forEachCostsDetailMultiBillingConfigs splits
// a monthly query into per-day sub-queries and aggregates all days correctly.
func (suite *CostExplorerSuite) TestDaySplittingBillsDetails() {
	assert := suite.Assert()

	bcID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(1)
	assert.NoError(err)
	unit := "daysplit-detail-" + uuid.NewString()
	suite.createStreamTestUnit(unit)

	// Insert data on 2 different days in the same month
	day0 := datainit.BillingDate
	day1 := datainit.BillingDate.AddDate(0, 0, 1)
	suite.insertDetailCost(1, bcID, day0.Unix(), unit, map[string]string{}, entity.ConsumedSource_Invoice, decimal.NewFromInt(10), decimal.NewFromInt(8), false)
	suite.insertDetailCost(1, bcID, day1.Unix(), unit, map[string]string{}, entity.ConsumedSource_Invoice, decimal.NewFromInt(20), decimal.NewFromInt(16), false)

	model := &costexplorer.CostExplorerModel{}
	// Monthly query — exercises day splitting in forEachCostsDetailMultiBillingConfigs
	// and forEachCostsDetailByInvoiceIDs (when called without invoice IDs)
	details, err := model.GetExportBillsDetailsByTenant([]uint{1}, nil, datainit.BillingDate.Year(), int(datainit.BillingDate.Month()), nil)
	assert.NoError(err)

	streamRows := filterExportByService(details)
	assert.GreaterOrEqual(len(streamRows), 1, "should have at least one aggregated row")

	// Verify that the total raw price across all rows sums to 30 (10 from day0 + 20 from day1)
	total := decimal.Zero
	for _, r := range streamRows {
		total = total.Add(r.Amount)
	}
	assert.True(total.GreaterThanOrEqual(decimal.NewFromInt(30)),
		"multi-day export should aggregate all days, got total %s", total)
}

// TestDaySplittingSingleDayNoSplit verifies that single-day queries are NOT split
// (the day-splitting logic correctly returns early when the range is <= 1 day).
func (suite *CostExplorerSuite) TestDaySplittingSingleDayNoSplit() {
	assert := suite.Assert()

	const tenantID uint = 1
	billingDate := time.Date(2021, 11, 15, 0, 0, 0, 0, time.UTC)

	err := datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	_, err = newCreditsPackageWhihTime(tenantID, 10, billingDate)
	assert.NoError(err)
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   "test_single_day_unit",
		Category:  "Node Compute Cost",
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)

	measureModel := &billing.MeasureModel{}
	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		CostTimeStamp: billingDate.Unix(),
		Unit:          "test_single_day_unit",
		Cost:          1,
	})
	assert.NoError(err)

	costExplorerModel := &costexplorer.CostExplorerModel{}
	// Single-day query — should NOT trigger day splitting
	query := &forms.CostExplorerQueryV2{
		StartDate: billingDate,
		EndDate:   billingDate.AddDate(0, 0, 1),
	}
	res, err := costExplorerModel.CalculateCostV2(tenantID, []uint{1}, query)
	assert.NoError(err)
	assert.NotNil(res)
	// Single-day query should still work without splitting
	assert.True(res.ExternalCosts.TotalCosts.CostPrice.GreaterThan(decimal.Zero),
		"single-day query should still work, got %s", res.ExternalCosts.TotalCosts.CostPrice)
}

// TestBillsExportDetailsFromUnitCostsUsageMerge covers mergeExportUsage branches:
// happy path (matching units accumulate usage) and mismatch path (different units zero out).
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsUsageMerge() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestRU",
			Cost:           2000,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(5),
			RawPrice:       decimal.NewFromInt(5),
		},
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestRU",
			Cost:           1000,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(3),
			RawPrice:       decimal.NewFromInt(3),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestRU",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "RU",
			Price:       decimal.NewFromFloat(0.001),
			PriceType:   entity.PriceType_Normal,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	// Two rows merged: usage accumulates raw_price/unit.price = 5000+3000=8000, amount 5+3=8
	assert.Equal(decimal.NewFromInt(8000), res[0].Usage)
	assert.Equal("RU", res[0].Unit)
	assert.Equal(decimal.NewFromInt(8), res[0].Amount)
	assert.Equal(decimal.NewFromInt(8), res[0].BillableCost)
}

// TestBillsExportDetailsFromUnitCostsIgnoresUnitMeasureUnit verifies export usage
// uses category_item_shows.measure_unit instead of Unit.MeasureUnit.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsIgnoresUnitMeasureUnit() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestRU_v1",
			Cost:           100,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(1),
			RawPrice:       decimal.NewFromInt(1),
		},
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestRU_v2",
			Cost:           200,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(1),
			RawPrice:       decimal.NewFromInt(1),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestRU_v1",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "WrongUnitA",
			Price:       decimal.NewFromFloat(0.01),
			PriceType:   entity.PriceType_Normal,
		},
		{
			UnitKey:     "TestRU_v2",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "WrongUnitB",
			Price:       decimal.NewFromFloat(0.005),
			PriceType:   entity.PriceType_Normal,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	assert.Equal(decimal.NewFromInt(300), res[0].Usage)
	assert.Equal("RU", res[0].Unit)
}

// TestBillsExportDetailsFromUnitCostsNonServerlessUsage checks that units without
// a supported category measure unit get empty Usage/Unit.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsNonServerlessUsage() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestBackup",
			Cost:           500,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(10),
			RawPrice:       decimal.NewFromInt(10),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestBackup",
			Category:    "Backup Cost",
			MeasureUnit: "GB",
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	assert.True(res[0].Usage.IsZero())
	assert.Equal("", res[0].Unit)
}

// TestBillsExportDetailsFromUnitCostsCreditsUsage checks that credit source
// calculates Usage/Unit while still recording cost as credits.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsCreditsUsage() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestRU",
			Cost:           500,
			ConsumedSource: entity.ConsumedSource_Credits,
			CostPrice:      decimal.NewFromInt(10),
			RawPrice:       decimal.NewFromInt(10),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestRU",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "RU",
			Price:       decimal.NewFromFloat(0.001),
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	// Credits source calculates usage from raw_price/unit.price, while billableCost=0 and creditUsage=10.
	assert.Equal(decimal.NewFromInt(10000), res[0].Usage)
	assert.Equal("RU", res[0].Unit)
	assert.Equal(decimal.Zero, res[0].BillableCost)
	assert.Equal(decimal.NewFromInt(10), res[0].CreditUsage)
}

// TestBillsExportDetailsFromUnitCostsIntervalType verifies that export usage
// reverse-calculate usage from raw_price / unit.price instead of using cost.Cost directly.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsIntervalType() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestIntervalRU",
			Cost:           100,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(60),
			RawPrice:       decimal.NewFromInt(60),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestIntervalRU",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "RU",
			Price:       decimal.NewFromFloat(0.0001),
			PriceType:   entity.PriceType_Interval,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	expectedUsage := decimal.NewFromInt(60).Div(decimal.NewFromFloat(0.0001))
	assert.Equal(expectedUsage, res[0].Usage)
	assert.Equal("RU", res[0].Unit)
}

// TestBillsExportDetailsFromUnitCostsIntervalNoPrice verifies units without a
// valid price get empty usage (cannot reverse-calculate).
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsIntervalNoPrice() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestIntervalRU",
			Cost:           100,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(60),
			RawPrice:       decimal.NewFromInt(60),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestIntervalRU",
			Category:    "Serverless Request Unit Cost",
			MeasureUnit: "RU",
			Price:       decimal.Zero,
			PriceType:   entity.PriceType_Interval,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	assert.True(res[0].Usage.IsZero())
	assert.Equal("", res[0].Unit)
}

// TestBillsExportDetailsFromUnitCostsThresholdSkips verifies that unsupported
// category measure units get empty Usage/Unit.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsThresholdSkips() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestThreshold",
			Cost:           500,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(100),
			RawPrice:       decimal.NewFromInt(100),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestThreshold",
			Category:    "Backup Cost",
			MeasureUnit: "Hours",
			Price:       decimal.NewFromInt(1),
			PriceType:   entity.PriceType_Threshold,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	assert.True(res[0].Usage.IsZero())
	assert.Equal("", res[0].Unit)
}

// TestBillsExportDetailsFromUnitCostsNormalZeroCost verifies that unsupported
// category measure units produce empty Usage/Unit.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsNormalZeroCost() {
	assert := suite.Assert()

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-10-01",
			Unit:           "TestNormal",
			Cost:           0,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(10),
			RawPrice:       decimal.NewFromInt(10),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:     "TestNormal",
			Category:    "Backup Cost",
			MeasureUnit: "GB",
			PriceType:   entity.PriceType_Normal,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	assert.Len(res, 1)
	assert.True(res[0].Usage.IsZero())
	assert.Equal("", res[0].Unit)
}

// TestBillsExportDetailsFromUnitCostsMergeSecondProvidesUnit covers the
// mergeExportUsage branch where the first merged row has an empty unit and the
// second row provides the category measure unit.
func (suite *CostExplorerSuite) TestBillsExportDetailsFromUnitCostsMergeSecondProvidesUnit() {
	assert := suite.Assert()
	assert.NoError((&entity.CategoryItemShow{
		Category:         "Merge Empty Measure Category",
		ShowKindName:     "Merge Product",
		ShowCategoryName: "Merge Service",
		OrderMap: entity.OrderMap{
			"Merge Product": 1,
			"Merge Service": 1,
		},
		UseCateGory: true,
	}).Create(db.GetDB()))
	assert.NoError((&entity.CategoryItemShow{
		Category:         "Merge Hours Category",
		ShowKindName:     "Merge Product",
		ShowCategoryName: "Merge Service",
		OrderMap: entity.OrderMap{
			"Merge Product": 1,
			"Merge Service": 1,
		},
		UseCateGory: true,
		MeasureUnit: "RU",
	}).Create(db.GetDB()))

	costs := []entity.CostsDetailsQuery{
		{
			TenantID:       1,
			BilledDate:     "2022-11-01",
			Unit:           "MergeA",
			Cost:           100,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(5),
			RawPrice:       decimal.NewFromInt(5),
		},
		{
			TenantID:       1,
			BilledDate:     "2022-11-01",
			Unit:           "MergeB",
			Cost:           200,
			ConsumedSource: entity.ConsumedSource_Invoice,
			CostPrice:      decimal.NewFromInt(3),
			RawPrice:       decimal.NewFromInt(3),
		},
	}
	units := []forms.UnitInfo{
		{
			UnitKey:   "MergeA",
			Category:  "Merge Empty Measure Category",
			PriceType: entity.PriceType_Threshold,
		},
		{
			UnitKey:   "MergeB",
			Category:  "Merge Hours Category",
			Price:     decimal.NewFromFloat(0.015),
			PriceType: entity.PriceType_Normal,
		},
	}

	res, err := costexplorer.BillsExportDetailsFromUnitCosts(costs, units)
	assert.NoError(err)
	// Both rows share the same service (same category) → merge into one row.
	// First row has empty usage/unit; second row provides unit + usage.
	assert.Len(res, 1)
	assert.Equal(decimal.NewFromInt(200), res[0].Usage)
	assert.Equal("RU", res[0].Unit)
	assert.Equal(decimal.NewFromInt(8), res[0].Amount)
}

// TestPartnerInvoicedBillsDetailsFromUnitCosts tests the partner invoiced bills details
// accumulator through its exported wrapper, covering add/merge paths.
// Note: tests omit MSPCustomerIDLabel to avoid triggering centralhandler.GetTenant external calls.
func (suite *CostExplorerSuite) TestPartnerInvoicedBillsDetailsFromUnitCosts() {
	assert := suite.Assert()

	invoiceMap := map[uint]costexplorer.InvoiceSummaryCache{
		1: {SentDate: "2022-10-01", PaymentCode: "PAY-001"},
	}
	costs := []entity.CostsDetailsQuery{
		{
			TenantID:              1,
			BindedBillingConfigID: 1,
			BilledDate:            "2022-10-01",
			Unit:                  "TestServerlessRU",
			CostPrice:             decimal.NewFromInt(50),
			RawPrice:              decimal.NewFromInt(50),
			ConsumedSource:        entity.ConsumedSource_Invoice,
			InvoiceID:             1,
		},
		{
			TenantID:              1,
			BindedBillingConfigID: 1,
			BilledDate:            "2022-10-01",
			Unit:                  "TestServerlessRU",
			CostPrice:             decimal.NewFromInt(30),
			RawPrice:              decimal.NewFromInt(30),
			ConsumedSource:        entity.ConsumedSource_Invoice,
			InvoiceID:             1,
		},
	}
	units := []forms.UnitInfo{
		{UnitKey: "TestServerlessRU", Category: "Serverless Request Unit Cost"},
	}

	res, err := costexplorer.PartnerBillsDetailsFromUnitCostsWithInvoicedDate(costs, units, invoiceMap, false)
	assert.NoError(err)
	assert.Len(res, 1)
	// Two rows merged: RunningTotal 50+30=80, TotalCost 50+30=80
	assert.Equal(decimal.NewFromInt(80), res[0].RunningTotal)
	assert.Equal(decimal.NewFromInt(80), res[0].TotalCost)
	assert.Equal("2022-10-01", res[0].InvoicedDate)
	assert.Equal("PAY-001", res[0].PaymentCode)
}

// TestPartnerInvoicedBillsDetailsCreditsSource covers the credits consumed source branch.
func (suite *CostExplorerSuite) TestPartnerInvoicedBillsDetailsCreditsSource() {
	assert := suite.Assert()

	invoiceMap := map[uint]costexplorer.InvoiceSummaryCache{
		1: {SentDate: "2022-12-01", PaymentCode: "PAY-003"},
	}
	costs := []entity.CostsDetailsQuery{
		{
			TenantID:              1,
			BindedBillingConfigID: 1,
			BilledDate:            "2022-12-01",
			Unit:                  "TestServerlessRU",
			CostPrice:             decimal.NewFromInt(15),
			RawPrice:              decimal.NewFromInt(15),
			ConsumedSource:        entity.ConsumedSource_Credits,
			InvoiceID:             1,
		},
	}
	units := []forms.UnitInfo{
		{UnitKey: "TestServerlessRU", Category: "Serverless Request Unit Cost"},
	}

	res, err := costexplorer.PartnerBillsDetailsFromUnitCostsWithInvoicedDate(costs, units, invoiceMap, false)
	assert.NoError(err)
	assert.Len(res, 1)
	// Credits source → TotalCost=0, Credits=15
	assert.Equal(decimal.Zero, res[0].TotalCost)
	assert.Equal(decimal.NewFromInt(15), res[0].Credits)
}

// TestPartnerInvoicedBillsDetailsDefaultCategory covers the empty-category default fallback.
func (suite *CostExplorerSuite) TestPartnerInvoicedBillsDetailsDefaultCategory() {
	assert := suite.Assert()

	invoiceMap := map[uint]costexplorer.InvoiceSummaryCache{
		1: {SentDate: "2023-01-01", PaymentCode: "PAY-004"},
	}
	costs := []entity.CostsDetailsQuery{
		{
			TenantID:              1,
			BindedBillingConfigID: 1,
			BilledDate:            "2023-01-01",
			Unit:                  "UnknownUnit",
			CostPrice:             decimal.NewFromInt(10),
			RawPrice:              decimal.NewFromInt(10),
			ConsumedSource:        entity.ConsumedSource_Invoice,
			InvoiceID:             1,
		},
	}
	units := []forms.UnitInfo{
		{UnitKey: "OtherUnit", Category: "Other"},
	}

	res, err := costexplorer.PartnerBillsDetailsFromUnitCostsWithInvoicedDate(costs, units, invoiceMap, false)
	assert.NoError(err)
	assert.Len(res, 1)
	assert.NotNil(res)
}

// =============================================================================
// Tests for the bills-precision fix (commit be7d8fb9).
//
// Commit 3bfb2859 introduced per-day-split + per-row Truncate, which loses 1¢
// per cross-day group whose daily sums each truncate to zero on some day.
// The fix reverts the per-day split from the default bills path and freezes
// June 2026 (which finance has already reconciled against the broken values)
// via a dedicated path. These tests pin the contract:
//
//   - June 2026: per-day split + per-row Truncate (commit 3bfb2859 result)
//   - Other months: single full-month scan + per-row Truncate
//     (pre-3bfb2859 cents-precise result)
//
// Each test inserts the same cross-day shape (Day 1 raw = 6.889, Day 2 raw =
// 0.001) under a fresh billing-month key, so the cache never serves a stale
// answer. Day 1's 6.889 truncates to 6.88 per-row; Day 2's 0.001 truncates to
// 0.00 per-row. The per-day-split path sums them to 6.88; the single-query
// path sees the full 6.890 and truncates to 6.89.
// =============================================================================

// seedJuneFreezeCrossDayData inserts two user_unit_costs rows for the same
// (tenant, unit, cluster, invoice) group on two consecutive days, with raw
// prices chosen so that the per-day-split + per-row-Truncate path returns
// 6.88 (1¢ less than the single-query correct answer 6.89).
func (suite *CostExplorerSuite) seedJuneFreezeCrossDayData(tenantID uint, unit string, day1, day2 time.Time) {
	bcID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
	suite.Assert().NoError(err)
	suite.insertDetailCost(tenantID, bcID, day1.Unix(), unit,
		map[string]string{constant.ClusterLabelKey: "p1"},
		entity.ConsumedSource_Invoice,
		decimal.RequireFromString("6.889"), decimal.RequireFromString("6.889"), false)
	suite.insertDetailCost(tenantID, bcID, day2.Unix(), unit,
		map[string]string{constant.ClusterLabelKey: "p1"},
		entity.ConsumedSource_Invoice,
		decimal.RequireFromString("0.001"), decimal.RequireFromString("0.001"), false)
}

// TestJune2026BillsUsePerDaySplitFreeze verifies that the bills interface for
// 2026-06 keeps the per-day-split + per-row-Truncate algorithm when the
// June2026DailySplitEnabled config flag is set. The test inserts a cross-day
// group whose per-day-split answer is 6.88 (Day 1: 6.889 truncated to 6.88;
// Day 2: 0.001 truncated to 0.00; sum 6.88) and the correct single-query
// answer is 6.89 (6.889 + 0.001 = 6.890 truncated to 6.89). With the flag on,
// June 2026 MUST continue to emit 6.88 because finance has already reconciled
// against this number — see commit be7d8fb9.
//
// Removing the June freeze (the `if config.Get().GetBool(...) && year==2026 && ...`
// block in GetMonthlyCost) will flip this test to expect 6.89. Update both
// expectations together when finance signs off on the 2026-06 reconciliation.
func (suite *CostExplorerSuite) TestJune2026BillsUsePerDaySplitFreeze() {
	assert := suite.Assert()
	const tenantID uint = 1
	billedMonth := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	unit := "june-freeze-unit-" + uuid.NewString()

	config.Get().Set(config.June2026DailySplitEnabled, true)
	defer config.Get().Set(config.June2026DailySplitEnabled, false)

	suite.seedJuneFreezeCrossDayData(tenantID, unit,
		billedMonth.AddDate(0, 0, 0), // Day 1
		billedMonth.AddDate(0, 0, 1), // Day 2
	)

	res, err := (&costexplorer.CostExplorerModel{}).CalculateOpenAPIMonthlyCostWithCache(
		tenantID, 1, billedMonth)
	assert.NoError(err)
	assert.NotNil(res)
	assert.True(decimal.RequireFromString("6.88").Equal(res.BillsOverView.BillsCosts.TotalCost),
		"June 2026 freeze: TotalCost got %s, want 6.88 (per-day-split + per-row-truncate, the value finance has reconciled against)",
		res.BillsOverView.BillsCosts.TotalCost)
}

// TestJune2026DailySplitFlagOffUsesSingleQuery verifies that the
// June2026DailySplitEnabled config flag controls whether the June 2026 bills
// freeze is applied. When the flag is false (the default), June 2026 bills use
// the correct single-query + per-row-Truncate algorithm and emit 6.89 for the
// same cross-day shape that the freeze path would have truncated to 6.88.
//
// This pins the contract that the freeze is opt-in: once finance signs off on
// the 2026-06 reconciliation, leaving the flag off (the default) is the safe
// state, and operators do not need to actively disable anything.
func (suite *CostExplorerSuite) TestJune2026DailySplitFlagOffUsesSingleQuery() {
	assert := suite.Assert()
	const tenantID uint = 1
	billedMonth := time.Date(2026, 6, 15, 0, 0, 0, 0, time.UTC)
	unit := "june-flag-off-unit-" + uuid.NewString()

	// Explicitly set false to guard against a leaked value from a prior test
	// in the same process. The package-level default is already false.
	config.Get().Set(config.June2026DailySplitEnabled, false)

	suite.seedJuneFreezeCrossDayData(tenantID, unit,
		billedMonth.AddDate(0, 0, 0), // Day 1
		billedMonth.AddDate(0, 0, 1), // Day 2
	)

	res, err := (&costexplorer.CostExplorerModel{}).CalculateOpenAPIMonthlyCostWithCache(
		tenantID, 1, billedMonth)
	assert.NoError(err)
	assert.NotNil(res)
	assert.True(decimal.RequireFromString("6.89").Equal(res.BillsOverView.BillsCosts.TotalCost),
		"June 2026 with flag off: TotalCost got %s, want 6.89 (single-query + per-row-truncate, cents-precise)",
		res.BillsOverView.BillsCosts.TotalCost)
}

// TestJuly2026BillsUseSingleQueryAlgorithm verifies that the bills interface for
// 2026-07 (immediately after the freeze window) uses the correct
// single-query + per-row-Truncate algorithm. With the same cross-day shape as
// TestJune2026BillsUsePerDaySplitFreeze, the correct answer is 6.89
// (6.889 + 0.001 = 6.890 truncated to 6.89). This test pins the "all months
// except 2026-06" contract.
func (suite *CostExplorerSuite) TestJuly2026BillsUseSingleQueryAlgorithm() {
	assert := suite.Assert()
	const tenantID uint = 1
	billedMonth := time.Date(2026, 7, 15, 0, 0, 0, 0, time.UTC)
	unit := "july-correct-unit-" + uuid.NewString()

	suite.seedJuneFreezeCrossDayData(tenantID, unit,
		billedMonth.AddDate(0, 0, 0),
		billedMonth.AddDate(0, 0, 1),
	)

	res, err := (&costexplorer.CostExplorerModel{}).CalculateOpenAPIMonthlyCostWithCache(
		tenantID, 1, billedMonth)
	assert.NoError(err)
	assert.NotNil(res)
	assert.True(decimal.RequireFromString("6.89").Equal(res.BillsOverView.BillsCosts.TotalCost),
		"July 2026 (post-freeze): TotalCost got %s, want 6.89 (single-query + per-row-truncate, cents-precise)",
		res.BillsOverView.BillsCosts.TotalCost)
}

// TestMay2026BillsUseSingleQueryAlgorithm verifies that the bills interface for
// 2026-05 — billed BEFORE commit 3bfb2859 was deployed — always used the
// correct single-query + per-row-Truncate algorithm. May 2026 is NOT in the
// freeze window (finance's 2026-06 reconciliation is the only special case)
// and MUST continue to emit the cents-precise 6.89, not the broken 6.88.
// This guards against an over-zealous future change that accidentally widens
// the freeze to 2026-05.
func (suite *CostExplorerSuite) TestMay2026BillsUseSingleQueryAlgorithm() {
	assert := suite.Assert()
	const tenantID uint = 1
	billedMonth := time.Date(2026, 5, 15, 0, 0, 0, 0, time.UTC)
	unit := "may-correct-unit-" + uuid.NewString()

	suite.seedJuneFreezeCrossDayData(tenantID, unit,
		billedMonth.AddDate(0, 0, 0),
		billedMonth.AddDate(0, 0, 1),
	)

	res, err := (&costexplorer.CostExplorerModel{}).CalculateOpenAPIMonthlyCostWithCache(
		tenantID, 1, billedMonth)
	assert.NoError(err)
	assert.NotNil(res)
	assert.True(decimal.RequireFromString("6.89").Equal(res.BillsOverView.BillsCosts.TotalCost),
		"May 2026 (pre-freeze): TotalCost got %s, want 6.89 (single-query + per-row-truncate, May was never on commit 3bfb2859)",
		res.BillsOverView.BillsCosts.TotalCost)
}
