package tests

import (
	"sort"
	"testing"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/model/marketing/unit_filter"
	"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"
)

var unitFilterModel unit_filter.Model

type CreditsPackageTestSuite struct {
	tests.BillingTestSuite
	generalType *forms.UnitFilter
}

func TestCreditsPackageSuite(t *testing.T) {
	suite.Run(t, new(CreditsPackageTestSuite))
}

func (s *CreditsPackageTestSuite) SetupTest() {
	s.BillingTestSuite.SetupTest()
	assert := s.Assert()
	err := datainit.CreatePaymentMock(1)
	assert.NoError(err)
	_, err = datainit.CreateBillingConfig(1, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)

	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(1),
		PriceType: entity.PriceType_Normal,
	})
	assert.NoError(err)
	generalUnitFilter, err := unitFilterModel.CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	s.generalType = generalUnitFilter

	assert.NoError(err)
	config.Get().Set(config.CreditsPricePackage, datainit.NoUsePackageId)
	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate
	}
}

var creditsPackageModel = &marketing.CreditsPackageModel{}

func (s *CreditsPackageTestSuite) TestCRUDTenantCreditsPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	creditsPackages, err := creditsPackageModel.ListTenantCreditsPackages(tenantID, 0)
	assert.NoError(err)
	assert.Len(creditsPackages, 0)
	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		Usage:                 entity.CreditsPackageUsage_POC,
		UnitFilters:           []*forms.UnitFilterSlim{{FilterID: s.generalType.FilterID}},
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.Equal(s.generalType.Name, info.Type)
	assert.Len(info.BindTypes, 1)
	assert.Equal(&forms.UnitFilterSlim{FilterID: s.generalType.TypeID, TypeID: s.generalType.TypeID, Name: s.generalType.Name, Description: s.generalType.Description, ParentID: s.generalType.ParentID}, info.BindTypes[0])

	creditsPackages, err = creditsPackageModel.ListTenantCreditsPackages(tenantID, 0)
	assert.NoError(err)
	assert.Len(creditsPackages, 1)

	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.NotNil(info.CreditsCode, info.CreditsCode)
}

func (s *CreditsPackageTestSuite) TestOpenCreditsPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})

	assert.NoError(err)

	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(info.Status, entity.CreditsPackageStatus_BeforeActive)
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.NotNil(info.BindedPackageID)

	err = creditsPackageModel.OpenCreditsPackage(info.CreditsPackageID)
	assert.Error(err)
	assert.ErrorIs(err, billing_errors.InvalidInputError)

	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate.AddDate(0, 0, 1)
	}
	concurrency := 10
	errChanList := make([]chan error, concurrency)
	for i := 0; i != concurrency; i++ {
		errChanList[i] = make(chan error)
		go func(ch chan error) {
			err = creditsPackageModel.OpenCreditsPackage(info.CreditsPackageID)
			assert.NoError(err)
			close(ch)
		}(errChanList[i])
	}
	for i := 0; i != concurrency; i++ {
		select {
		case <-errChanList[i]:
			continue
		case <-time.After(2 * time.Second):
			panic("timeout")
		}
	}
	assert.NoError(err)

	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(info.Status, entity.CreditsPackageStatus_Active)
	assert.Equal(info.RemainingCredits, decimal.NewFromInt(100))
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.NotNil(info.BindedPackageID)
	bindedPackage, err := (&billing.BindedPackageModel{}).GetBindedPackage(*info.BindedPackageID)
	assert.NoError(err)
	assert.EqualValues(bindedPackage.PackageID, config.Get().GetUint(config.CreditsPricePackage))
	assert.Equal(bindedPackage.Active, entity.BindedPackageStatus_Active)
	assert.Equal(bindedPackage.RemainingCredits, decimal.NewFromInt(100))

	cnt, err := entity.NewBindedPackageQuerySet(db.GetDB()).TenantIDEq(tenantID).Count()
	assert.Nil(err)
	assert.Equal(int64(1), cnt)

	err = creditsPackageModel.OpenCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
}

func (s *CreditsPackageTestSuite) TestCreateWithOpenPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})

	assert.NoError(err)

	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(info.Status, entity.CreditsPackageStatus_Active)
	assert.Equal(info.RemainingCredits, decimal.NewFromInt(100))
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.NotNil(info.BindedPackageID)
	bindedPackage, err := (&billing.BindedPackageModel{}).GetBindedPackage(*info.BindedPackageID)
	assert.NoError(err)
	assert.EqualValues(bindedPackage.PackageID, config.Get().GetUint(config.CreditsPricePackage))
	assert.Equal(bindedPackage.Active, entity.BindedPackageStatus_Active)
	assert.Equal(bindedPackage.RemainingCredits, decimal.NewFromInt(100))
}

func (s *CreditsPackageTestSuite) TestCloseOpenedCreditsPackage() {
	timeutils.SetMockNow(datainit.BillingDate)
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})

	assert.NoError(err)

	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(info.Status, entity.CreditsPackageStatus_Active)
	assert.Equal(info.RemainingCredits, decimal.NewFromInt(100))
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.NotNil(info.BindedPackageID)

	concurrency := 10
	errChanList := make([]chan error, concurrency)
	for i := 0; i != concurrency; i++ {
		errChanList[i] = make(chan error)
		go func(ch chan error) {
			err = creditsPackageModel.CloseCreditsPackage(info.CreditsPackageID, "test")
			assert.NoError(err)
			close(ch)
		}(errChanList[i])
	}
	for i := 0; i != concurrency; i++ {
		select {
		case <-errChanList[i]:
			continue
		case <-time.After(2 * time.Second):
			panic("timeout")
		}
	}

	closedInfo, err := creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(closedInfo.Status, entity.CreditsPackageStatus_InActive)
	assert.Equal(closedInfo.RemainingCredits, decimal.NewFromInt(100))
	assert.Equal(closedInfo.TotalCredits, decimal.NewFromInt(100))
	assert.Equal(closedInfo.ClosedReason, "test")
	assert.NotNil(closedInfo.BindedPackageID)
	bindedPackageInfo, err := (&billing.BindedPackageModel{}).GetBindedPackage(*closedInfo.BindedPackageID)
	assert.NoError(err)
	assert.Equal(bindedPackageInfo.Active, entity.BindedPackageStatus_Active)

	timeutils.SetMockNow(datainit.BillingDate.Add(config.Get().GetDuration(config.BindedPackageInactiveDelayHour) * time.Hour))
	_, err = (&billing.BindedPackageModel{}).InactiveTimeoutBindedPackages()
	assert.NoError(err)
	bindedPackageInfo, err = (&billing.BindedPackageModel{}).GetBindedPackage(*closedInfo.BindedPackageID)
	assert.NoError(err)
	assert.Equal(bindedPackageInfo.Active, entity.BindedPackageStatus_InActive)

	err = creditsPackageModel.CloseCreditsPackage(info.CreditsPackageID, "test")
	assert.NoError(err)
}

func (s *CreditsPackageTestSuite) TestCloseBeforeActiveCreditsPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 1, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})

	assert.NoError(err)

	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(info.Status, entity.CreditsPackageStatus_BeforeActive)
	assert.Equal(info.RemainingCredits, decimal.NewFromInt(100))
	assert.Equal(info.TotalCredits, decimal.NewFromInt(100))
	assert.NotNil(info.BindedPackageID)

	bp, err := (&billing.BindedPackageModel{}).GetBindedPackage(*info.BindedPackageID)
	assert.NoError(err)
	assert.Equal(bp.RemainingCredits, info.TotalCredits)
	assert.Equal(*bp.StartedDate, info.StartDate)
	assert.Equal(*bp.ExpiredDate, info.ExpiredDate)

	err = creditsPackageModel.CloseCreditsPackage(info.CreditsPackageID, "test")
	assert.NoError(err)

	closedInfo, err := creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)

	assert.Equal(closedInfo.Status, entity.CreditsPackageStatus_InActive)
	assert.Equal(closedInfo.RemainingCredits, decimal.NewFromInt(100))
	assert.Equal(closedInfo.TotalCredits, decimal.NewFromInt(100))
	assert.Equal(closedInfo.ClosedReason, "test")
}

func (s *CreditsPackageTestSuite) TestGetAllNeededOpenCreditsPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	_, err = creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	info1, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	info2, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate.AddDate(0, 0, 1)
	}
	err = creditsPackageModel.OpenCreditsPackage(info1.CreditsPackageID)
	assert.NoError(err)
	err = creditsPackageModel.OpenCreditsPackage(info2.CreditsPackageID)
	assert.NoError(err)
	err = creditsPackageModel.CloseCreditsPackage(info2.CreditsPackageID, "")
	assert.NoError(err)

	ids, err := creditsPackageModel.GetAllNeedOpenPackagesIDs()
	assert.NoError(err)
	assert.Len(ids, 1)
	assert.Equal(info.CreditsPackageID, ids[0])
}

func (s *CreditsPackageTestSuite) TestGetAllExpiredOpenCreditsPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	info3, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	info1, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	info2, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate.AddDate(0, 1, 1)
	}
	err = creditsPackageModel.OpenCreditsPackage(info1.CreditsPackageID)
	assert.NoError(err)
	err = creditsPackageModel.OpenCreditsPackage(info2.CreditsPackageID)
	assert.NoError(err)
	err = creditsPackageModel.CloseCreditsPackage(info2.CreditsPackageID, "")
	assert.NoError(err)

	ids, err := creditsPackageModel.GetAllExpiredPackageIDs()
	assert.NoError(err)
	assert.Len(ids, 3)
	sort.Slice(ids, func(i, j int) bool {
		return ids[i] < ids[j]
	})
	expectedIDs := []uint{}
	expectedIDs = append(expectedIDs, info.CreditsPackageID)
	expectedIDs = append(expectedIDs, info1.CreditsPackageID)
	expectedIDs = append(expectedIDs, info3.CreditsPackageID)
	sort.Slice(expectedIDs, func(i, j int) bool {
		return expectedIDs[i] < expectedIDs[j]
	})
	assert.Equal(ids, expectedIDs)
}

func (s *CreditsPackageTestSuite) TestGetAllExhaustedPackage() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	_, err = creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	info2, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	err = creditsPackageModel.CloseCreditsPackage(info2.CreditsPackageID, "")
	assert.NoError(err)
	_, err = (&billing.BindedPackageModel{}).ChargeCredits(*info.BindedPackageID, &forms.CreditsChargeInfo{
		ChargeCount: decimal.NewFromInt(-100),
	})
	assert.NoError(err)

	ids, err := creditsPackageModel.GetAllExhaustedPackageIDs()
	assert.NoError(err)
	assert.Len(ids, 1)
	assert.Equal(ids[0], info.CreditsPackageID)
}

func (s *CreditsPackageTestSuite) TestCreditsPackageMeasures() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	assert.Equal(info.Status, entity.CreditsPackageStatus_BeforeActive)
	measureModel := &billing.MeasureModel{}
	r, err := measureModel.CostUnits(tenantID, &forms.UnitCosts{
		Unit:          datainit.TestUnit,
		CostTimeStamp: datainit.BillingDate.Unix(),
		Cost:          1,
	})
	assert.NoError(err)
	assert.Equal(entity.ConsumedSource_None, r.ConsumedSource)
	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate.AddDate(0, 0, 1).Add(time.Second)
	}
	err = creditsPackageModel.OpenCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		Unit:          datainit.TestUnit,
		CostTimeStamp: datainit.BillingDate.AddDate(0, 0, 1).Unix(),
		Cost:          1,
	})
	assert.NoError(err)
	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
	assert.Equal(info.RemainingCredits, decimal.NewFromInt(99))

	err = creditsPackageModel.CloseCreditsPackage(info.CreditsPackageID, "")
	assert.NoError(err)

	r, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		Unit:          datainit.TestUnit,
		CostTimeStamp: datainit.BillingDate.Unix(),
		Cost:          1,
	})
	assert.NoError(err)
	assert.Equal(entity.ConsumedSource_None, r.ConsumedSource)

	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		Unit:          datainit.TestUnit,
		CostTimeStamp: datainit.BillingDate.AddDate(0, 0, 1).Unix(),
		Cost:          1,
	})
	assert.NoError(err)
	info, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
	assert.Equal(info.RemainingCredits, decimal.NewFromInt(98))

}

func (s *CreditsPackageTestSuite) TestCreditsPackageMeasuresPriority() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	info1, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 1),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	measureModel := &billing.MeasureModel{}

	for i := 0; i < 10; i++ {
		_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
			Unit:          datainit.TestUnit,
			CostTimeStamp: datainit.BillingDate.Unix(),
			Cost:          1,
		})
		assert.NoError(err)
		result, err := creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
		assert.NoError(err)
		assert.Equal(result.RemainingCredits, decimal.NewFromInt(100))
		result, err = creditsPackageModel.GetCreditsPackage(info1.CreditsPackageID)
		assert.NoError(err)
		assert.Equal(result.RemainingCredits, decimal.NewFromInt(int64(100-i-1)))
	}
}

func (s *CreditsPackageTestSuite) TestGetCreditsSummary() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	summary, err := creditsPackageModel.GetTenantCreditsSummary(tenantID, 0)
	assert.NoError(err)
	assert.Equal(summary.TotalCredits, decimal.Zero)
	assert.Equal(summary.LastCreditsClosedDate, &marketing.DefaultClosedDate)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 1),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	summary, err = creditsPackageModel.GetTenantCreditsSummary(tenantID, 0)
	assert.NoError(err)
	assert.Equal(summary.TotalCredits, decimal.NewFromInt(100))
	assert.Equal(summary.AvailableRemainingCredits, decimal.NewFromInt(100))
	assert.Equal(summary.AvailableCredits, decimal.NewFromInt(100))
	assert.Nil(summary.LastCreditsClosedDate)

	err = creditsPackageModel.CloseCreditsPackage(*info.BindedPackageID, "")
	assert.NoError(err)
	summary, err = creditsPackageModel.GetTenantCreditsSummary(tenantID, 0)
	assert.NoError(err)
	assert.Equal(summary.TotalCredits, decimal.NewFromInt(100))
	assert.EqualValues(summary.LastCreditsClosedDate, &datainit.BillingDate)
	assert.Equal(summary.AvailableRemainingCredits, decimal.Zero)
	assert.Equal(summary.AvailableCredits, decimal.Zero)

	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate.AddDate(0, 0, 1)
	}

	info1, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 2),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	summary, err = creditsPackageModel.GetTenantCreditsSummary(tenantID, 0)
	assert.NoError(err)
	assert.Equal(summary.TotalCredits, decimal.NewFromInt(200))
	assert.Nil(summary.LastCreditsClosedDate)

	err = creditsPackageModel.CloseCreditsPackage(*info1.BindedPackageID, "")
	assert.NoError(err)
	summary, err = creditsPackageModel.GetTenantCreditsSummary(tenantID, 0)
	assert.NoError(err)
	assert.Equal(summary.TotalCredits, decimal.NewFromInt(200))
	assert.EqualValues(*summary.LastCreditsClosedDate, datainit.BillingDate.AddDate(0, 0, 1))
}

func (s *CreditsPackageTestSuite) TestCreditsPackageRemainPrecision() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey:   datainit.TestUnit,
		Price:     decimal.NewFromInt(1).Shift(-3),
		PriceType: entity.PriceType_Normal,
	})
	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	measureModel := &billing.MeasureModel{}

	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		Unit:          datainit.TestUnit,
		CostTimeStamp: datainit.BillingDate.Unix(),
		Cost:          6,
	})
	assert.NoError(err)
	result, err := creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
	assert.Equal(result.RemainingCredits, decimal.NewFromInt(100))

	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		Unit:          datainit.TestUnit,
		CostTimeStamp: datainit.BillingDate.Unix(),
		Cost:          6,
	})
	assert.NoError(err)
	result, err = creditsPackageModel.GetCreditsPackage(info.CreditsPackageID)
	assert.NoError(err)
	assert.Equal(result.RemainingCredits, decimal.RequireFromString("99.99"))
}

func (s *CreditsPackageTestSuite) TestGetAllCreditsOpenedTenants() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	_, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 1),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)

	tenantIDs, err := creditsPackageModel.GetAllCreditsOpenedTenantIDs()
	assert.NoError(err)
	assert.Len(tenantIDs, 1)
	assert.Equal(tenantIDs[0].TenantID, tenantID)
}

func (s *CreditsPackageTestSuite) TestCreateDiscountEnabledCredits() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 1),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
		DiscountEnabled:       true,
	})
	assert.NoError(err)
	assert.True(info.DiscountEnabled)

	tenantIDs, err := creditsPackageModel.GetAllCreditsOpenedTenantIDs()
	assert.NoError(err)
	assert.Len(tenantIDs, 1)
	assert.Equal(tenantIDs[0].TenantID, tenantID)

	bps, err := (&billing.BindedPackageModel{}).GetAllTenantBindedPackage(tenantID)
	assert.NoError(err)
	assert.Len(bps, 1)
	assert.True(bps[0].DiscountEnabled)
}

func (s *CreditsPackageTestSuite) TestCreateDiscountDisabledCredits() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	info, err := creditsPackageModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 1),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
		DiscountEnabled:       false,
	})
	assert.NoError(err)
	assert.False(info.DiscountEnabled)

	tenantIDs, err := creditsPackageModel.GetAllCreditsOpenedTenantIDs()
	assert.NoError(err)
	assert.Len(tenantIDs, 1)
	assert.Equal(tenantIDs[0].TenantID, tenantID)

	bps, err := (&billing.BindedPackageModel{}).GetAllTenantBindedPackage(tenantID)
	assert.NoError(err)
	assert.Len(bps, 1)
	assert.False(bps[0].DiscountEnabled)
}

func (s *CreditsPackageTestSuite) TestInternalCredits() {
	var err error
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	req := &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 0, 1),
		TotalCredits:          decimal.NewFromInt(100),
		BindedBillingConfigID: &billingConfigID,
	}
	externalCredits, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	assert.False(externalCredits.Internal)
	assert.Equal(entity.CreditsPackageStatus_Active, externalCredits.Status)
	externalCreditsBindedPackage, err := (&billing.BindedPackageModel{}).GetBindedPackage(*externalCredits.BindedPackageID)
	assert.NoError(err)
	assert.False(externalCreditsBindedPackage.Internal)

	req.Internal = true
	internalCredits, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	assert.True(internalCredits.Internal)
	assert.Equal(entity.CreditsPackageStatus_Active, internalCredits.Status)
	internalCreditsBindedPackage, err := (&billing.BindedPackageModel{}).GetBindedPackage(*internalCredits.BindedPackageID)
	assert.NoError(err)
	assert.True(internalCreditsBindedPackage.Internal)

	allListCredits, err := creditsPackageModel.ListTenantAllCreditsPackages(tenantID)
	assert.Len(allListCredits, 2)

	allExternalCredits, err := creditsPackageModel.ListTenantCreditsPackages(tenantID, 0)
	assert.Len(allExternalCredits, 1)
	assert.False(allExternalCredits[0].Internal)

	req.Usage = entity.CreditsPackageUsage_POC
	req.Internal = false
	_, err = creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.False(externalCredits.Internal)
	summary, err := creditsPackageModel.GetTenantCreditsSummary(tenantID, 0)
	assert.Equal(decimal.NewFromInt(200), summary.AvailableCredits)
	assert.Equal(decimal.NewFromInt(200), summary.AvailableRemainingCredits)
	assert.Equal(decimal.NewFromInt(100), summary.AvailablePOCCredits)
	assert.Equal(decimal.NewFromInt(100), summary.AvailableRemainingPOCCredits)
}

func (s *CreditsPackageTestSuite) TestListCreditsPackages() {
	var err error
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)

	req := &forms.CreditsPackage{
		StartDate:             datainit.BillingDate.AddDate(0, 0, 1),
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(100),
		Usage:                 entity.CreditsPackageUsage_Prepayment,
		BindedBillingConfigID: &billingConfigID,
	}
	credits1, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	req.StartDate = datainit.BillingDate.AddDate(0, 0, -20)
	req.ExpiredDate = datainit.BillingDate.AddDate(0, 0, 1)
	credits2, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	req.StartDate = datainit.BillingDate.AddDate(0, 0, -20)
	req.ExpiredDate = datainit.BillingDate.AddDate(0, 0, -10)
	credits3, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	req.StartDate = datainit.BillingDate.AddDate(0, 0, 10)
	req.ExpiredDate = datainit.BillingDate.AddDate(0, 0, 12)
	tenantID++
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	credits4, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	req.Usage = entity.CreditsPackageUsage_Refund
	credits5, err := creditsPackageModel.CreateCreditsPackage(tenantID, req)
	assert.NoError(err)
	creditsList, err := creditsPackageModel.ListCreditsPackages(forms.CreditsPackageFilter{
		StartDate:   timeutils.GetTimeAddr(datainit.BillingDate),
		ExpiredDate: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 1, 0)),
		Usage:       entity.CreditsPackageUsage_Prepayment,
	}, 0)
	assert.NoError(err)
	assert.Len(creditsList, 3)
	targets := []uint{credits4.CreditsPackageID, credits2.CreditsPackageID, credits1.CreditsPackageID}
	for i, credits := range creditsList {
		assert.Equal(targets[i], credits.CreditsPackageID)
	}
	creditsList, err = creditsPackageModel.ListCreditsPackages(forms.CreditsPackageFilter{
		StartDate:   timeutils.GetTimeAddr(datainit.BillingDate),
		ExpiredDate: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 1, 0)),
		Usage:       entity.CreditsPackageUsage_Refund,
	}, 0)
	assert.NoError(err)
	assert.Len(creditsList, 1)
	assert.Equal(credits5.CreditsPackageID, creditsList[0].CreditsPackageID)
	creditsList, err = creditsPackageModel.ListCreditsPackages(forms.CreditsPackageFilter{
		StartDate:   timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, -1, 0)),
		ExpiredDate: timeutils.GetTimeAddr(datainit.BillingDate),
		Usage:       entity.CreditsPackageUsage_Prepayment,
	}, 0)
	assert.NoError(err)
	assert.Len(creditsList, 2)
	targets = []uint{credits3.CreditsPackageID, credits2.CreditsPackageID}
	for i, credits := range creditsList {
		assert.Equal(targets[i], credits.CreditsPackageID)
	}
	assert.Equal(credits3.CreditsPackageID, creditsList[0].CreditsPackageID)
}

func (s *CreditsPackageTestSuite) TestListAllPrepaymentCreditsPackages() {
	assert := s.Assert()
	for i := 0; i != 5; i++ {
		tenantID := uint(i + 1)
		if i != 0 {
			datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
		}
		billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)
		assert.NoError(err)
		_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{PackageID: config.Get().GetUint(config.CreditsPricePackage), BindedBillingConfigID: &billingConfigID})
		assert.NoError(err)
	}
	sql := "insert into credits_packages (`created_at`,`updated_at`,`deleted_at`,`tenant_id`,`status`,`credits_code`,`start_date`,`expired_date`,`closed_date`,`binded_package_id`,`total_credits`,`usage`,`discount_enabled`,`internal`,`binded_billing_config_id`)  values" +
		"(\"2024-02-28 10:55:34.064\",\"2024-03-01 00:03:57.488\",NULL,1,\"active\",\"7184b048-241f-4368-bbf5-53c11951e782\",\"2024-03-01 00:00:00.000\",\"2025-02-28 23:59:59.000\",NULL,1 , 100000.000000000000000 ,      \"prepayment\",                1 ,        0 ,                    1)," +
		"(\"2024-03-14 06:50:28.383\",\"2024-04-01 00:02:17.408\",NULL,2,\"active\",\"2249cd4d-0d59-4a9f-9920-6fcbae2f22f5\",\"2024-04-01 00:00:00.000\",\"2025-03-31 23:59:59.000\",NULL,2 ,  30000.000000000000000 ,      \"prepayment\",                1 ,        0 ,                    2)," +
		"(\"2024-03-14 07:51:06.917\",\"2024-03-14 07:51:06.931\",NULL,3,\"active\",\"6c367a16-42d0-43ed-9b73-5b691fefa9ae\",\"2024-03-14 00:00:00.000\",\"2025-01-08 23:59:59.000\",NULL,3 , 137452.850000000000000 ,      \"prepayment\",                1 ,        0 ,                    3)," +
		"(\"2024-03-27 09:58:41.588\",\"2024-04-01 00:02:17.410\",NULL,4,\"active\",\"83339a6d-bd9c-4fa9-949b-9293c1dea921\",\"2024-04-01 00:00:00.000\",\"2025-03-31 23:59:59.000\",NULL,4 ,  50000.000000000000000 ,      \"prepayment\",                1 ,        0 ,                    4)," +
		"(\"2024-03-28 02:36:06.001\",\"2024-04-01 00:02:17.410\",NULL,5,\"active\",\"ba56ee7d-0827-4146-b5c8-b903095138a4\",\"2024-04-01 00:00:00.000\",\"2025-03-31 23:59:59.000\",NULL,5 ,  50000.000000000000000 ,      \"prepayment\",                1 ,        0 ,                    5)"
	assert.NoError(db.GetDB().Exec(sql).Error)
	start := timeutils.GetMonthDate(time.Date(2024, 4, 3, 0, 0, 0, 0, time.UTC))
	end := start.AddDate(0, 1, 0)
	credits, err := creditsPackageModel.ListAllPrepaymentCreditsPackages(&start, &end)
	assert.NoError(err)
	assert.Len(credits, 5)
	end = start
	start = start.AddDate(0, -1, 0)
	credits, err = creditsPackageModel.ListAllPrepaymentCreditsPackages(&start, &end)
	assert.NoError(err)
	assert.Len(credits, 2)

}
