package workers

import (
	"errors"
	"fmt"
	"slices"
	"testing"
	"time"

	"github.com/google/uuid"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/handler/infrahandler"

	"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/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler/impl"
	"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/model/payment"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/model/payment/spend_limit"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
	"golang.org/x/exp/rand"
)

type WorkerTestSuite struct {
	tests.BillingTestSuite
}

func TestInvoiceWorker(t *testing.T) {
	suite.Run(t, new(WorkerTestSuite))
}

func (s *WorkerTestSuite) TestRunWorker() {
	tests.SetupConfig()
	called := false
	config.Get().Set(config.InvoiceWorkerCheckInterval, "1s")
	config.Get().Set(config.RechargeCreditWorkerInterval, "1h")
	CreateInvoiceWorker = func() {
		called = true
	}
	RunWorker(0)
	time.Sleep(time.Second * 2)
	assert := s.Assert()
	assert.True(called)
}

type MockCentralHandlerImpl struct {
	centralhandler.CentralHandler
	tenants []forms.Tenant
}

func (*MockCentralHandlerImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	if tenantID == 211 {
		return &forms.Tenant{
			TenantID: tenantID,
			Status:   centralhandler.TenantStatusActive,
			IsMsp:    true,
		}, nil
	}
	return &forms.Tenant{
		TenantID: tenantID,
		Status:   centralhandler.TenantStatusActive,
	}, nil
}

func (i *MockCentralHandlerImpl) SetAllTenants(tenants []forms.Tenant) {
	i.tenants = tenants
}

func (i *MockCentralHandlerImpl) GetAllTenants() ([]forms.Tenant, error) {
	return i.tenants, nil
}

func (*MockCentralHandlerImpl) DownloadFile(string) ([]byte, error) {
	return nil, nil
}

func (*MockCentralHandlerImpl) GetUser(userID uint) (*forms.User, error) {
	return &forms.User{
		UserID: userID,
	}, nil
}

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

func mockCentral() {
	central := &MockCentralHandlerImpl{}
	central.SetAllTenants([]forms.Tenant{
		{
			TenantID: 1,
			Status:   centralhandler.TenantStatusActive,
		},
		{
			TenantID: 211,
			Status:   centralhandler.TenantStatusActive,
			IsMsp:    true,
		},
	})
	centralhandler.SetCentralHandler(central)
}

func (s *WorkerTestSuite) TestCreateMonthlyInvoice() {
	assert := s.Assert()
	config.Get().Set(config.EnableInvoiceWorker, true)

	invoicehours := config.Get().GetInt(config.Invoicehours)

	now := datainit.BillingDate.AddDate(0, 1, 0).Add(time.Duration(invoicehours) * time.Hour)
	timeutils.GetNowFunc = func() time.Time {
		return now
	}

	datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)

	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	mockCentral()
	m := &MockPaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)

	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	invoice, err := invoiceModel.GetMonthlyInvoice(1, 1, datainit.BillingDate)
	assert.NoError(err)
	assert.Equal(invoice.StartDate, datainit.BillingDate)
}

func (s *WorkerTestSuite) TestCreateInMonthInvoice() {
	assert := s.Assert()
	var err error
	config.Get().Set(config.EnableInvoiceWorker, true)

	invoiceDealy := config.Get().GetInt(config.InMonthInvoiceHours)

	now := datainit.BillingDate.AddDate(0, 1, 0).Add(-time.Duration(invoiceDealy) * time.Hour)
	timeutils.GetNowFunc = func() time.Time {
		return now
	}
	_, err = datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)

	err = datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	mockCentral()
	m := &MockMarketplacePaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)

	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	invoices, err := invoiceModel.ListAllInvoicesWithType(1, 1, true, entity.InvoiceType_INMONTH)
	assert.NoError(err)
	assert.Len(invoices, 1)
	assert.Equal(invoices[0].Status, entity.InvoiceStatus_SENT)
	assert.Equal(datainit.BillingDate, invoices[0].StartDate)
	invoices, err = invoiceModel.ListAllInvoicesWithType(1, 1, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(invoices, 0)

	err = datainit.SendMeasureWithTime(1, datainit.BillingDate, 1)
	assert.NoError(err)
	CreateInvoiceWorkerFunc()
	invoices, err = invoiceModel.ListAllInvoicesWithType(1, 1, true, entity.InvoiceType_INMONTH)
	assert.NoError(err)
	assert.Len(invoices, 1)
	invoices, err = invoiceModel.ListAllInvoicesWithType(1, 1, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(invoices, 0)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(config.Get().GetDuration(config.Invoicehours) * time.Hour))
	CreateInvoiceWorkerFunc()
	invoices, err = invoiceModel.ListAllInvoicesWithType(1, 1, true, entity.InvoiceType_INMONTH)
	assert.NoError(err)
	assert.Len(invoices, 1)
	invoices, err = invoiceModel.ListAllInvoicesWithType(1, 1, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(invoices, 1)
	assert.Equal(invoices[0].Status, entity.InvoiceStatus_SENT)
	assert.Equal(datainit.BillingDate, invoices[0].StartDate)
}

func (s *WorkerTestSuite) TestCreateDailyInvoice() {
	assert := s.Assert()
	var err error
	tenantID := uint(773312)
	_, err = datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(err)
	config.Get().Set(config.EnableInvoiceWorker, true)
	billingConfig, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfig(tenantID)
	assert.NoError(err)

	delayDays := config.Get().GetInt(config.DailyInvoiceIssueDelayDays)
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 0, delayDays).Add(-time.Second))
	err = datainit.CreateMeasures(tenantID)
	assert.NoError(err)
	mockCentral()
	m := &MockPaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)
	config.Get().Set(config.DailyInvoiceTenantBillingConfigIDList, []string{fmt.Sprintf("%d-%d", tenantID, billingConfig.BillingConfigID)})
	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	monthlyInvoices, err := invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(monthlyInvoices, 0)
	dailyInvoices, err := invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 0)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 0, delayDays))
	CreateInvoiceWorkerFunc()
	dailyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 1)
	assert.Equal(datainit.BillingDate, dailyInvoices[0].StartDate)
	assert.Equal(decimal.NewFromInt(1), dailyInvoices[0].CostPrice)

	assert.NoError(datainit.SendMeasureWithTime(tenantID, datainit.BillingDate, 1))
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 0, delayDays+1).Add(-time.Second))
	CreateInvoiceWorkerFunc()
	monthlyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(monthlyInvoices, 0)
	dailyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 1)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 0, delayDays+config.Get().GetInt(config.MaxHourlyInvoiceReseekDays)))
	CreateInvoiceWorkerFunc()
	monthlyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(monthlyInvoices, 0)
	dailyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 2)

	assert.NoError(datainit.SendMeasureWithTime(tenantID, datainit.BillingDate, 1))
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 0, delayDays+config.Get().GetInt(config.MaxHourlyInvoiceReseekDays)).Add(time.Second))
	CreateInvoiceWorkerFunc()
	dailyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 2)
	monthlyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(monthlyInvoices, 0)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(time.Hour*config.Get().GetDuration(config.Invoicehours) - time.Second))
	CreateInvoiceWorkerFunc()
	dailyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 2)
	monthlyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(monthlyInvoices, 0)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(time.Hour * config.Get().GetDuration(config.Invoicehours)))
	CreateInvoiceWorkerFunc()
	dailyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_DAILY)
	assert.NoError(err)
	assert.Len(dailyInvoices, 2)
	monthlyInvoices, err = invoiceModel.ListAllInvoicesWithType(tenantID, billingConfig.BillingConfigID, true, entity.InvoiceType_MONTHLY)
	assert.NoError(err)
	assert.Len(monthlyInvoices, 1)
	assert.Equal(datainit.BillingDate, monthlyInvoices[0].StartDate)
}

type MockMSPPaymentProvider struct {
	mock.Mock
	paymenthandler.PaymentProvider
}

func (*MockMSPPaymentProvider) EnableHourlySending(tenantID, billingConfigID uint) bool {
	return true
}

func (*MockMSPPaymentProvider) EnableDailySending(tenantID, billingConfigID uint) (bool, int) {
	return false, 0
}

func (*MockMSPPaymentProvider) NeedInMonthInvoice() bool {
	return false
}

func (*MockMSPPaymentProvider) NeedSetInvoiceAutoConfirm() bool {
	return false
}

func (s *WorkerTestSuite) TestCreateMSPMonthlyInvoice() {
	assert := s.Assert()
	var err error
	config.Get().Set(config.EnableInvoiceWorker, true)

	invoicehours := config.Get().GetInt(config.Invoicehours)

	now := datainit.BillingDate.AddDate(0, 1, 0).Add(time.Duration(invoicehours) * time.Hour)
	now = now.Add(time.Duration(config.Get().GetInt(config.PartnerTenantInvoicehours)) * time.Hour)
	timeutils.GetNowFunc = func() time.Time {
		return now
	}
	_, err = datainit.CreateBillingConfig(uint(211), entity.BillingConfigType_MSP)
	assert.NoError(err)
	err = datainit.CreateMeasures(uint(211))
	assert.NoError(err)
	mockCentral()
	m := &MockMSPPaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)

	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	invoice, err := invoiceModel.GetMonthlyInvoice(211, 1, datainit.BillingDate)
	assert.NoError(err)
	assert.Equal(invoice.StartDate, datainit.BillingDate)

}

func initMarketplacePayment(tenantID uint) error {
	err := datainit.CreatePaymentMock(tenantID)
	if err != nil {
		return err
	}
	err = datainit.CreatePricePackageAndBind(tenantID)
	if err != nil {
		return err
	}

	billingConfigID := uint(1)
	tests.MockCentralHandler()
	config.Get().Set(config.AWSPaymentEnable, false)
	_, err = (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(
		tenantID,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           "aws",
			BindedBillingConfigID: &billingConfigID,
		},
	)
	if err != nil {
		return err
	}
	return nil
}

func createMarketplaceMeasures(tenantID uint, costTimeStamp int64) error {
	measureModel := &billing.MeasureModel{}
	_, err := measureModel.CostUnits(tenantID, &forms.UnitCosts{
		CostTimeStamp: costTimeStamp,
		Unit:          datainit.TestUnit,
		Cost:          1,
	})
	if err != nil {
		return err
	}
	return nil
}

type MockMarketplacePaymentProvider struct {
	mock.Mock
	paymenthandler.PaymentProvider
}

func (*MockMarketplacePaymentProvider) EnableHourlySending(tenantID, billingConfigID uint) bool {
	return false
}

func (*MockMarketplacePaymentProvider) EnableDailySending(tenantID, billingConfigID uint) (bool, int) {
	return false, 0
}

func (*MockMarketplacePaymentProvider) NeedMultiInvoiceSendOnce() bool {
	return true
}
func (*MockMarketplacePaymentProvider) NeedSetInvoiceAutoConfirm() bool {
	return true
}
func (*MockMarketplacePaymentProvider) EnableTaxCalculation() bool {
	return false
}

func (*MockMarketplacePaymentProvider) PayMultiInvoice(paymentInfo *forms.PaymentMethod, billingCompany *forms.BillingCompany, invoices []forms.Invoice) ([]forms.Invoice, error) {
	return invoices, nil
}

func (*MockMarketplacePaymentProvider) NeedInMonthInvoice() bool {
	return true
}

func (s *WorkerTestSuite) TestCreateMarketPlaceHourlyInvoice() {
	assert := s.Assert()

	config.Get().Set(config.EnableInvoiceWorker, true)

	now := datainit.BillingDate

	timeutils.GetNowFunc = func() time.Time {
		return now
	}
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	err := initMarketplacePayment(tenantID)
	assert.NoError(err)
	err = createMarketplaceMeasures(tenantID, datainit.BillingDate.Add(-time.Hour*12).Unix())
	assert.NoError(err)

	mockCentral()
	m := &MockMarketplacePaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)

	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	invoices, err := invoiceModel.ListAllInvoices(1, 1, false)
	assert.Equal(invoices[0].Type, entity.InvoiceType_INMONTH)
	assert.NoError(err)
	assert.Len(invoices, 1)
	invoice := invoices[0]
	assert.Equal(invoice.StartDate, datainit.BillingDate.AddDate(0, -1, 0))
	//2011-11-1  +24
	timeutils.SetMockNow(datainit.BillingDate.Add(time.Hour * 24))
	err = createMarketplaceMeasures(tenantID, datainit.BillingDate.Add(time.Hour*12).Unix())
	assert.NoError(err)
	CreateInvoiceWorkerFunc()
	invoices, err = invoiceModel.ListAllInvoices(1, 1, false)
	assert.NoError(err)
	assert.Len(invoices, 1)
	invoice = invoices[0]
	assert.Equal(invoice.StartDate, datainit.BillingDate.AddDate(0, -1, 0))
	//2011-11-1  +24h + 1m+2d
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 2).Add(2 * time.Hour))
	CreateInvoiceWorkerFunc()
	invoices, err = invoiceModel.ListAllInvoices(1, 1, false)
	assert.NoError(err)
	assert.Len(invoices, 2)
	assert.Equal(invoices[1].StartDate, datainit.BillingDate.AddDate(0, -1, 0))
	assert.Equal(invoices[0].StartDate, datainit.BillingDate)

	err = createMarketplaceMeasures(tenantID, datainit.BillingDate.Add(time.Hour*16).Unix())
	CreateInvoiceWorkerFunc()
	invoices, err = invoiceModel.ListAllInvoices(1, 1, false)
	assert.Len(invoices, 3)
	if invoices[1].InvoiceID == 3 {
		assert.Equal(invoices[1].Type, entity.InvoiceType_MONTHLY)

	}
	if invoices[2].InvoiceID == 3 {
		assert.Equal(invoices[1].Type, entity.InvoiceType_MONTHLY)

	}

	err = createMarketplaceMeasures(tenantID, datainit.BillingDate.Add(time.Hour*32).Unix())
	CreateInvoiceWorkerFunc()
	invoices, err = invoiceModel.ListAllInvoices(1, 1, false)
	assert.Len(invoices, 3)

}

func (s *WorkerTestSuite) TestCreateMonthlyInvoiceDisabledConfig() {
	assert := s.Assert()
	config.Get().Set(config.EnableInvoiceWorker, false)
	now := datainit.BillingDate.AddDate(0, 1, 0)
	timeutils.GetNowFunc = func() time.Time {
		return now
	}
	datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)

	mockCentral()

	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	_, err = invoiceModel.GetMonthlyInvoice(1, 1, datainit.BillingDate)
	assert.Error(err)
	assert.ErrorIs(err, billing_errors.ObjectNotExistsError)
}

type MockPaymentProvider struct {
	mock.Mock
	paymenthandler.PaymentProvider
}

func (m *MockPaymentProvider) Payable(tenantID uint, userCode string) (bool, error) {
	return m.Called().Bool(0), nil
}

func (*MockPaymentProvider) Setup(tenant *forms.Tenant) (string, error) {
	return "mock_user_code", nil
}

func (*MockPaymentProvider) EnableHourlySending(tenantID, billingConfigID uint) bool {
	return false
}

func (*MockPaymentProvider) EnableDailySending(tenantID, billingConfigID uint) (bool, int) {
	return slices.Contains(config.Get().GetStringSlice(config.DailyInvoiceTenantBillingConfigIDList), fmt.Sprintf("%d-%d", tenantID, billingConfigID)), config.Get().GetInt(config.DailyInvoiceIssueDelayDays)
}

func (*MockPaymentProvider) NeedInMonthInvoice() bool {
	return false
}
func (*MockPaymentProvider) NeedSetInvoiceAutoConfirm() bool {
	return false
}

type MockPaymentHandler struct {
	Provider paymenthandler.PaymentProvider
}

func (m *MockPaymentHandler) GetProvider(string) (paymenthandler.PaymentProvider, error) {
	return m.Provider, nil
}

func (m *MockPaymentHandler) AllProviders() []string {
	return []string{"mock"}
}

func (s *WorkerTestSuite) TestSyncPayable() {
	assert := s.Assert()
	config.Get().Set(config.EnableSyncPayableWorker, true)
	mockCentral()
	m := &MockPaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)
	m.On("Payable").Return(false).Once()
	datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)
	paymentModel := &paymentsub.PaymentModel{}
	_, err := paymentModel.CreatePaymentMethodAndActive(1, &forms.PaymentMethodCreateQuery{
		PaymentType:           entity.PaymentType_STRIPE,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	payable, err := paymentModel.Payable(1)
	assert.NoError(err)
	assert.False(payable)
	m.On("Payable").Return(true).Once()
	SyncTenantPayableWorker()
	payable, err = paymentModel.Payable(1)
	assert.NoError(err)
	assert.True(payable)
}

func (s *WorkerTestSuite) TestRemoveUnusedCosts() {
	assert := s.Assert()
	datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)

	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	now := datainit.BillingDate.Add(time.Hour * 24 * 800)
	mockCentral()
	timeutils.GetNowFunc = func() time.Time {
		return now
	}

	costEntity := &entity.UserUnitCost{
		CostID:                "test",
		TenantID:              1,
		CostTimeStamp:         datainit.BillingDate.Unix(),
		Unit:                  "not_exists",
		Cost:                  1,
		Status:                entity.UserUnitCostStatus_Merged,
		BindedBillingConfigID: 1,
	}
	costEntity.Create(db.GetDB())

	cnt, err := entity.NewUserUnitCostQuerySet(db.GetDB()).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 2)
	err = RemoveUnusedCosts()
	assert.NoError(err)

	cnt, err = entity.NewUserUnitCostQuerySet(db.GetDB()).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 0)
}

func (s *WorkerTestSuite) TestMergeSmallCosts() {
	s.T().Skip("replace to online merging")
	assert := s.Assert()
	datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)

	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	now := datainit.BillingDate.Add(time.Hour * 24 * config.Get().GetDuration(config.MergeSmallCostsDays))
	mockCentral()
	timeutils.GetNowFunc = func() time.Time {
		return now
	}
	measureModel := billing.MeasureModel{}

	for i := range [10]int{} {
		_, err = measureModel.CostUnits(uint(1), &forms.UnitCosts{
			CostTimeStamp: datainit.BillingDate.Unix() + int64(i),
			Unit:          datainit.TestUnit,
			Cost:          1,
		})
		assert.NoError(err)
	}
	cnt, err := entity.NewUserUnitCostQuerySet(db.GetDB()).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 11)
	config.Get().Set(config.MergeSmallCostsOffsetSeconds, 86401)
	err = MergeSmallCosts()
	assert.Error(err)
	config.Get().Set(config.MergeSmallCostsOffsetSeconds, 86400)
	err = MergeSmallCosts()
	assert.NoError(err)
	cnt, err = entity.NewUserUnitCostQuerySet(db.GetDB()).StatusEq(entity.UserUnitCostStatus_DONE).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 1)
	cnt, err = entity.NewUserUnitCostQuerySet(db.GetDB()).StatusEq(entity.UserUnitCostStatus_DONE).TypeEq(entity.UserUnitCostType_Raw).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 0)
	err = MarkMergedCostsToRaw()
	assert.NoError(err)
	cnt, err = entity.NewUserUnitCostQuerySet(db.GetDB()).StatusEq(entity.UserUnitCostStatus_DONE).TypeEq(entity.UserUnitCostType_Raw).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 1)
}

func (s *WorkerTestSuite) TestSendSupportPlanMetering() {
	assert := s.Assert()
	model := &marketing.SupportPlanModel{}

	err := model.CreateSupportPlanUnits()
	assert.NoError(err)
	now := datainit.BillingDate
	timeutils.SetNowFunc(func() time.Time {
		return now
	})

	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)
	err = datainit.CreatePaymentMock(tenantID)
	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       true,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})

	mockCentral()

	assert.NoError(err)

	info, err := model.CreateSupportPlan(&forms.SupportPlan{
		FixedChargePrice: decimal.NewFromInt(15),
		Type:             entity.SupportPlanType_FixedPrice,
		Public:           true,
		Level:            entity.SupportPlanLevel_Standard,
	})
	assert.NoError(err)
	_, err = model.BindSupportPlanForPayableCustomer(tenantID, info.SupportPlanID, forms.DateInterval{StartDate: timeutils.GetTimeAddr(timeutils.GetDayDate(timeutils.GetNow()))})
	assert.NoError(err)

	now = now.AddDate(0, 0, 1)
	timeutils.SetNowFunc(func() time.Time {
		return now
	})

	err = SendSupportPlanMetering()
	assert.NoError(err)

	res, err := (&costexplorer.CostExplorerModel{}).GetMonthlyCost(tenantID, 1, now.Year(), int(now.Month()))
	assert.NoError(err)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(15).Div(decimal.NewFromInt(30)))
}

func (s *WorkerTestSuite) TestMspSendSupportPlanMetering() {
	assert := s.Assert()
	model := &marketing.SupportPlanModel{}

	err := model.CreateSupportPlanUnits()
	assert.NoError(err)
	billTime := datainit.BillingDate
	timeutils.SetNowFunc(func() time.Time {
		return billTime
	})

	tenantID := uint(211)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_MSP)
	billingConfigID := uint(1)
	err = datainit.CreatePaymentMock(tenantID)
	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       true,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})

	mockCentral()

	assert.NoError(err)
	input, err := decimal.NewFromString("0.5")

	info, err := model.CreateSupportPlan(&forms.SupportPlan{
		FixedChargePrice:       decimal.NewFromInt(15),
		Type:                   entity.SupportPlanType_PercentagePrice,
		Public:                 true,
		Level:                  entity.SupportPlanLevel_Standard,
		SupportPricePercentage: input,
	})
	assert.NoError(err)
	_, err = model.BindSupportPlanForPayableCustomer(tenantID, info.SupportPlanID, forms.DateInterval{StartDate: timeutils.GetTimeAddr(timeutils.GetDayDate(timeutils.GetNow()))})
	assert.NoError(err)
	costEntity := &entity.UserUnitCost{
		CostID:   "test-1-1",
		TenantID: tenantID,
		//"2021-11-01T00:00:00Z"
		CostTimeStamp:         timeutils.GetNow().Unix(),
		Unit:                  "test",
		Cost:                  20,
		CostPrice:             decimal.NewFromInt(100),
		Status:                entity.UserUnitCostStatus_DONE,
		InvoiceID:             1,
		BindedBillingConfigID: 1,
	}
	err = costEntity.Create(db.GetDB())
	assert.NoError(err)

	billTime2 := billTime.Add(60 * time.Hour)
	timeutils.SetNowFunc(func() time.Time {
		return billTime2
	})

	err = SendSupportPlanMetering()
	assert.NoError(err)
	res, err := (&costexplorer.CostExplorerModel{}).GetMonthlyCost(tenantID, 1, billTime2.Year(), int(billTime2.Month()))
	assert.NoError(err)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(100))
	// msp config 	Get().SetDefault(PartnerSupportPlanHours, 76)
	billTime3 := billTime.Add(76 * time.Hour)
	timeutils.SetNowFunc(func() time.Time {
		return billTime3
	})
	err = SendSupportPlanMetering()
	assert.NoError(err)

	res, err = (&costexplorer.CostExplorerModel{}).GetMonthlyCost(tenantID, 1, billTime3.Year(), int(billTime3.Month()))
	assert.NoError(err)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(150))
}

func (s *WorkerTestSuite) TestCloseSupportPlan() {
	assert := s.Assert()
	model := &marketing.SupportPlanModel{}

	err := model.CreateSupportPlanUnits()
	assert.NoError(err)
	billTime := datainit.BillingDate
	timeutils.SetNowFunc(func() time.Time {
		return billTime
	})

	tenantID := uint(211)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_MSP)
	billingConfigID := uint(1)
	err = datainit.CreatePaymentMock(tenantID)
	_, err = (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
		PackageID:             datainit.NoUsePackageId,
		DiscountEnabled:       true,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})

	mockCentral()

	assert.NoError(err)
	input, err := decimal.NewFromString("0.5")

	info, err := model.CreateSupportPlan(&forms.SupportPlan{
		FixedChargePrice:       decimal.NewFromInt(15),
		Type:                   entity.SupportPlanType_PercentagePrice,
		Public:                 true,
		Level:                  entity.SupportPlanLevel_Standard,
		SupportPricePercentage: input,
	})
	assert.NoError(err)
	bs, err := model.BindSupportPlanForPayableCustomer(tenantID, info.SupportPlanID, forms.DateInterval{StartDate: timeutils.GetTimeAddr(timeutils.GetDayDate(datainit.BillingDate)), EndDate: timeutils.GetTimeAddr(timeutils.GetDayDate(datainit.BillingDate.AddDate(0, 1, 0)).Add(-1 * time.Minute))})
	assert.NoError(err)
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 2).Add(7 * time.Hour))
	SyncSupportPlanStatusByTenant(tenantID, timeutils.GetNow())
	err = SendSupportPlanMetering()
	bs, err = model.GetBindedSupportPlan(bs.BindedSupportPlanID)
	assert.NoError(err)
	assert.Equal(entity.BindedSupportPlanStatus_Expired, bs.Status)
	assert.NoError(err)
	res, err := (&costexplorer.CostExplorerModel{}).GetMonthlyCost(tenantID, 1, datainit.BillingDate.Year(), int(datainit.BillingDate.Month()))
	assert.NoError(err)
	assert.Equal(res.InvoiceCostsCosts.CostPrice, decimal.NewFromInt(15))
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 3).Add(1 * time.Minute))
	SyncSupportPlanStatusByTenant(tenantID, timeutils.GetNow())
	bs, err = model.GetBindedSupportPlan(bs.BindedSupportPlanID)
	assert.NoError(err)
	assert.Equal(entity.BindedSupportPlanStatus_CANCELED, bs.Status)
}

func (s *WorkerTestSuite) TestTime228() {
	assert := s.Assert()
	var testDate = time.Date(2022, 11, 3, 1, 0, 0, 0, time.UTC)
	startTime := testDate.AddDate(0, -1, 0)
	assert.Equal(int(startTime.Month()), 10)
	var testDate228 = time.Date(2023, 03, 31, 1, 0, 0, 0, time.UTC)
	startTime = testDate228.AddDate(0, -1, 0)
	assert.NotEqual(int(startTime.Month()), 2)

	if startTime.Day() != testDate228.Day() {
		startTime = time.Date(startTime.Year(), startTime.Month(), 0, 0, 0, 0, 0, time.UTC)
	}
	assert.Equal(int(startTime.Month()), 2)

}

func (s *WorkerTestSuite) TestMonthDateForMonthlyWorker() {
	assert := s.Assert()

	testTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC)
	monthStartTime := timeutils.GetMonthDateForMonthlyWorkerByHours(testTime, 2)

	assert.Equal(int(monthStartTime.Month()), 11)
	assert.Equal(monthStartTime.Year(), 2022)

	testTime = time.Date(2023, 1, 2, 0, 0, 0, 0, time.UTC)
	monthStartTime = timeutils.GetMonthDateForMonthlyWorkerByHours(testTime, 48)
	assert.Equal(int(monthStartTime.Month()), 11)
	assert.Equal(monthStartTime.Year(), 2022)

	testTime = time.Date(2023, 1, 3, 0, 0, 0, 0, time.UTC)
	monthStartTime = timeutils.GetMonthDateForMonthlyWorkerByHours(testTime, 48)
	assert.Equal(int(monthStartTime.Month()), 12)
	assert.Equal(monthStartTime.Year(), 2022)
	fmt.Println(monthStartTime)

}
func (s *WorkerTestSuite) TestSupportPlanWorker() {
	assert := s.Assert()

	testTime := time.Date(2023, 1, 2, 19, 0, 0, 0, time.UTC)
	var meteringTime time.Time
	delayHours := 42
	if int(testTime.Sub(timeutils.GetMonthDate(testTime)).Hours()) > delayHours {
		meteringTime = timeutils.GetLastMonth(testTime)
	} else {
		meteringTime = timeutils.GetLast2Month(testTime)
	}
	assert.Equal(int(meteringTime.Month()), 12)
	assert.Equal(meteringTime.Year(), 2022)

	testTime = time.Date(2023, 1, 2, 17, 0, 0, 0, time.UTC)
	if int(testTime.Sub(timeutils.GetMonthDate(testTime)).Hours()) > delayHours {
		meteringTime = timeutils.GetLastMonth(testTime)
	} else {
		meteringTime = timeutils.GetLast2Month(testTime)
	}
	assert.Equal(int(meteringTime.Month()), 11)
	assert.Equal(meteringTime.Year(), 2022)

	testTime = time.Date(2023, 1, 2, 17, 0, 0, 0, time.UTC)
	meteringTime = timeutils.GetLastMonth(testTime)

	assert.Equal(int(meteringTime.Month()), 12)
	assert.Equal(meteringTime.Year(), 2022)
}
func (s *WorkerTestSuite) TestCheckAllSpendLimit() {
	spendLimitModel := &spend_limit.SpendLimitModel{}
	assert := s.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_DirectCustomer)

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

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

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

	err = CheckAllSpendLimit()
	assert.NoError(err)

	concurrency := 20
	central := &MockCentralHandlerImpl{}
	tenants := make([]forms.Tenant, concurrency)
	for i := 0; i != concurrency; i++ {
		tenants[i] = forms.Tenant{
			TenantID: uint(i + 1),
			Status:   centralhandler.TenantStatusActive,
		}
	}
	central.SetAllTenants(tenants)
	centralhandler.SetCentralHandler(central)
	chanList1 := make([]chan struct{}, concurrency)
	chanList2 := make([]chan struct{}, concurrency)
	for i := 0; i != concurrency; i++ {
		chanList1[i] = make(chan struct{})
		chanList2[i] = make(chan struct{})
		go func(tid uint, ch1 chan struct{}, ch2 chan struct{}) {
			setting, err := spendLimitModel.CreateSpendLimitSetting(tid, &forms.SpendLimitSetting{
				Interval:  entity.SpendLimitInterval_Monthly,
				CostType:  entity.SpendLimitCostType_Total,
				Limit:     decimal.RequireFromString("0.5"),
				RangeType: entity.SpendLimitRangeType_Tenant,
				RangeID:   tid,
			})
			assert.NoError(err)
			close(ch1)
			time.Sleep(time.Millisecond * time.Duration(500*rand.Float64()))
			err = spendLimitModel.DeleteSpendLimitSetting(tid, setting.SpendLimitSettingID)
			assert.NoError(err)
			close(ch2)
		}(tenantID+uint(i), chanList1[i], chanList2[i])
	}
	for i := 0; i != concurrency; i++ {
		select {
		case <-chanList1[i]:
		case <-time.After(time.Second):
			panic("timeout")
		}
		err = CheckAllSpendLimit()
		assert.NoError(err)

	}
	for i := 0; i != concurrency; i++ {
		select {
		case <-chanList2[i]:
		case <-time.After(time.Second):
			panic("timeout")
		}
	}
}

func (s *WorkerTestSuite) TestCheckSpendLimitByTenantInBatches() {
	spendLimitModel := &spend_limit.SpendLimitModel{}
	assert := s.Assert()
	tenantID := uint(501)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

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

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

	totalSpendLimits := spendLimitCheckBatchSize*2 + 13
	ids := make([]uint, 0, totalSpendLimits)
	for i := 0; i < totalSpendLimits; i++ {
		setting, err := spendLimitModel.CreateSpendLimitSetting(tenantID, &forms.SpendLimitSetting{
			Name:      fmt.Sprintf("batched-spend-limit-%d", i),
			Interval:  entity.SpendLimitInterval_Monthly,
			CostType:  entity.SpendLimitCostType_Total,
			Limit:     decimal.RequireFromString("0.5"),
			RangeType: entity.SpendLimitRangeType_Tenant,
			RangeID:   tenantID,
		})
		assert.NoError(err)
		ids = append(ids, setting.SpendLimitSettingID)
	}

	err = entity.NewSpendLimitSettingQuerySet(db.GetDB()).TenantIDEq(tenantID).IDIn(ids...).GetUpdater().SetStatus(entity.SpendLimitStatus_Fit).Update()
	assert.NoError(err)

	CheckSpendLimitByTenant(tenantID)

	var checked []entity.SpendLimitSetting
	err = entity.NewSpendLimitSettingQuerySet(db.GetDB()).TenantIDEq(tenantID).IDIn(ids...).All(&checked)
	assert.NoError(err)
	assert.Len(checked, totalSpendLimits)
	for _, setting := range checked {
		assert.Equal(entity.SpendLimitStatus_Excessive, setting.Status)
	}
}

func (s *WorkerTestSuite) TestCheckSpendLimitBatchAdvancesCursorOnDeletedSpendLimit() {
	assert := s.Assert()
	spendLimits := []forms.SpendLimitSetting{
		{TenantID: 1, SpendLimitSettingID: 100},
		{TenantID: 1, SpendLimitSettingID: 101},
	}

	var checkedIDs []uint
	lastSpendLimitID := checkSpendLimitBatch(spendLimits, 0, func(tenantID uint, spendLimitSettingID uint) error {
		checkedIDs = append(checkedIDs, spendLimitSettingID)
		if spendLimitSettingID == 101 {
			return billing_errors.ObjectNotExistsError
		}
		return nil
	})

	assert.Equal([]uint{100, 101}, checkedIDs)
	assert.Equal(uint(101), lastSpendLimitID)
}

func (s *WorkerTestSuite) TestCheckSpendLimitBatchEmpty() {
	assert := s.Assert()
	called := false
	lastSpendLimitID := checkSpendLimitBatch(nil, 42, func(tenantID uint, spendLimitSettingID uint) error {
		called = true
		return nil
	})
	assert.False(called)
	// An empty batch must leave the cursor untouched so the caller can terminate.
	assert.Equal(uint(42), lastSpendLimitID)
}

func (s *WorkerTestSuite) TestCheckSpendLimitBatchAdvancesCursorOnGenericError() {
	assert := s.Assert()
	spendLimits := []forms.SpendLimitSetting{
		{TenantID: 1, SpendLimitSettingID: 7},
		{TenantID: 1, SpendLimitSettingID: 8},
		{TenantID: 1, SpendLimitSettingID: 9},
	}

	var checkedIDs []uint
	lastSpendLimitID := checkSpendLimitBatch(spendLimits, 0, func(tenantID uint, spendLimitSettingID uint) error {
		checkedIDs = append(checkedIDs, spendLimitSettingID)
		if spendLimitSettingID == 8 {
			return errors.New("transient failure")
		}
		return nil
	})

	// A non-ObjectNotExists error is logged but must neither stop iteration nor stall the cursor.
	assert.Equal([]uint{7, 8, 9}, checkedIDs)
	assert.Equal(uint(9), lastSpendLimitID)
}

func (s *WorkerTestSuite) TestRechargeWorker() {
	mockCentral()
	var err error
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert := s.Assert()
	_, err = (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey: datainit.TestUnit,
	})
	assert.NoError(err)
	var unitFilterModel unit_filter.Model
	generalUnitFilter, err := unitFilterModel.CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	packageInfo, err := (&billing.PackageModel{}).CreatePackage(&forms.PackageInfo{
		UnitPrices: forms.UnitPrices{
			datainit.TestUnit: {Price: decimal.NewFromInt(1)},
		},
		Discount:      decimal.NewFromInt(1),
		CreditsEnable: true,
		CreditsOnly:   true,
	})
	assert.NoError(err)
	config.Get().Set(config.CreditsPricePackage, packageInfo.PackageID)
	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate
	}
	config.Get().Set(config.RechargeCreditsBeforeSeconds, 10800)
	req := &forms.RechargeCreditsPlan{
		Value:       decimal.NewFromInt(1),
		UnitFilters: []*forms.UnitFilterSlim{&generalUnitFilter.UnitFilterSlim},
		Cycle:       entity.RechargePlanCycle_Month,
		Rule: forms.RechargeCreditsPlanRule{
			FirstCycleStartedAt:   forms.FirstCycleStartedAt_NextStartOfNaturalCycle,
			FirstCycleFinishedAt:  forms.FirstCycleFinishedAt_EndOfNaturalCycle,
			FirstCreditsConvertor: forms.FirstCreditsConvertor_Full,
		},
		StartDate:   timeutils.GetNow(),
		ExpiredDate: timeutils.GetNow().AddDate(0, 1, 0),
		Duration:    time.Hour * 31 * 24,
	}
	info, err := rechargeModel.CreateRechargePlan(req)
	assert.NoError(err)
	_, err = rechargeModel.EnableTenantRechargePlan(tenantID, info.PlanID)
	assert.NoError(err)
	err = RechargeCreditWorker()
	assert.NoError(err)
	err = ExpireCreditRechargePlan()
	assert.NoError(err)

	var ent entity.TenantRechargeCreditsPlan
	err = entity.NewTenantRechargeCreditsPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).One(&ent)
	assert.NoError(err)
	assert.Equal(uint(0), ent.RechargeTimes)
	assert.Equal(datainit.BillingDate.AddDate(0, 1, 0), ent.NextCycleStartTime)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(-time.Duration(int64(time.Second) * (1 + config.Get().GetInt64(config.RechargeCreditsBeforeSeconds)))))
	err = RechargeCreditWorker()
	assert.NoError(err)
	err = ExpireCreditRechargePlan()
	assert.NoError(err)
	err = entity.NewTenantRechargeCreditsPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).One(&ent)
	assert.NoError(err)
	assert.Equal(uint(0), ent.RechargeTimes)
	assert.Equal(datainit.BillingDate.AddDate(0, 1, 0), ent.NextCycleStartTime)
	info, err = rechargeModel.GetRechargePlan(info.PlanID)
	assert.NoError(err)
	assert.Nil(info.ClosedAt)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(-time.Duration(int64(time.Second) * config.Get().GetInt64(config.RechargeCreditsBeforeSeconds))))
	err = RechargeCreditWorker()
	assert.NoError(err)
	err = ExpireCreditRechargePlan()
	assert.NoError(err)
	err = entity.NewTenantRechargeCreditsPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).One(&ent)
	assert.NoError(err)
	assert.Equal(uint(1), ent.RechargeTimes)
	var record entity.RechargeCreditsPlanRecord
	err = entity.NewRechargeCreditsPlanRecordQuerySet(db.GetDB()).MainPlanIDEq(info.PlanID).One(&record)
	assert.NoError(err)
	cp, err := (&marketing.CreditsPackageModel{}).GetCreditsPackage(record.CreditsPackageID)
	assert.NoError(err)
	assert.Len(cp.UnitFilters, len(req.UnitFilters))

	assert.Equal(datainit.BillingDate.AddDate(0, 2, 0), ent.NextCycleStartTime)
	info, err = rechargeModel.GetRechargePlan(info.PlanID)
	assert.NoError(err)
	assert.Nil(info.ClosedAt)

	err = RechargeCreditWorker()
	assert.NoError(err)
	err = ExpireCreditRechargePlan()
	assert.NoError(err)
	err = entity.NewTenantRechargeCreditsPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).One(&ent)
	assert.NoError(err)
	assert.Equal(uint(1), ent.RechargeTimes)
	assert.Nil(ent.ClosedAt)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(1 * time.Second))
	err = RechargeCreditWorker()
	assert.NoError(err)
	err = ExpireCreditRechargePlan()
	assert.NoError(err)
	info, err = rechargeModel.GetRechargePlan(info.PlanID)
	assert.NoError(err)
	assert.NotNil(info.ClosedAt)

	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 2, 0).Add(-time.Duration(int64(time.Second) * (config.Get().GetInt64(config.RechargeCreditsBeforeSeconds) - 1))))
	err = RechargeCreditWorker()
	assert.NoError(err)
	err = ExpireCreditRechargePlan()
	assert.NoError(err)
	err = entity.NewTenantRechargeCreditsPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).One(&ent)
	assert.NoError(err)
	assert.Equal(uint(2), ent.RechargeTimes)
	assert.NotNil(ent.ClosedAt)
	info, err = rechargeModel.GetRechargePlan(info.PlanID)
	assert.NotNil(info.ClosedAt)
	err = entity.NewRechargeCreditsPlanRecordQuerySet(db.GetDB()).MainPlanIDEq(info.PlanID).OrderDescByCreatedAt().One(&record)
	assert.NoError(err)
	cp, err = (&marketing.CreditsPackageModel{}).GetCreditsPackage(record.CreditsPackageID)
	assert.NoError(err)
	assert.Len(cp.UnitFilters, len(req.UnitFilters))
}

type InfraHandlerMock struct {
	infrahandler.InfraHandler
	clusters []*forms.Cluster
}

func (h *InfraHandlerMock) ListDedicatedClusters(tenantID uint) (ret []*forms.Cluster, err error) {
	return h.clusters, nil
}

func (s *WorkerTestSuite) TestNoTrafficDedicatedOrgChecker() {
	datainit.CreateDefaultUnits()
	datainit.CreateCategoryLabels()
	assert := s.Assert()
	central := &MockCentralHandlerImpl{}
	tenants := []forms.Tenant{
		{
			TenantID: 1,
			Kind:     forms.TenantKindExternal,
		},
	}
	central.SetAllTenants(tenants)
	centralhandler.SetCentralHandler(central)
	infrahandler.Set(&InfraHandlerMock{
		clusters: []*forms.Cluster{{ID: 1, TenantID: 1}, {ID: 2, TenantID: 1}},
	})
	billingConfigs, err := datainit.CreateBillingConfig(uint(1), entity.BillingConfigType_MarketplaceReseller)
	assert.NoError(err)
	for i, cid := range []string{"1", "2"} {
		nodeCost := entity.UserUnitCost{
			CostID:                uuid.NewString(),
			CostTimeStamp:         datainit.BillingDate.Unix(),
			CostPrice:             decimal.NewFromInt(2),
			RawPrice:              decimal.NewFromInt(2),
			TenantID:              uint(1),
			Status:                entity.UserUnitCostStatus_DONE,
			ConsumedSource:        entity.ConsumedSource_Invoice,
			BindedBillingConfigID: billingConfigs.BillingConfigID,
			Unit:                  datainit.AWSNodeUnit,
			Labels:                map[string]string{constant.ClusterLabelKey: cid},
		}
		assert.NoError(nodeCost.Create(db.GetDB()))
		if i != 0 {
			trafficCost := entity.UserUnitCost{
				CostID:                uuid.NewString(),
				CostTimeStamp:         datainit.BillingDate.Unix(),
				CostPrice:             decimal.NewFromInt(1),
				RawPrice:              decimal.NewFromInt(1),
				TenantID:              uint(1),
				Status:                entity.UserUnitCostStatus_DONE,
				ConsumedSource:        entity.ConsumedSource_Invoice,
				BindedBillingConfigID: billingConfigs.BillingConfigID,
				Unit:                  datainit.AWSDataTransferCrossAZUnit,
				Labels:                map[string]string{constant.ClusterLabelKey: cid},
			}
			assert.NoError(trafficCost.Create(db.GetDB()))
		}
	}
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0))
	assert.NoError(NoTrafficDedicatedOrgCheckWorker())
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0).Add(config.Get().GetDuration(config.Invoicehours)*time.Hour + time.Minute))
	assert.NoError(NoTrafficDedicatedOrgCheckWorker())
}

func (s *WorkerTestSuite) TestAliyunHourBoundarySkip() {
	assert := s.Assert()
	tenantID := uint(1)
	billingConfigID := uint(1)
	testAliUid := "5962436752113096"

	config.Get().Set(config.EnableInvoiceWorker, true)
	config.Get().Set(config.AliyunPaymentEnable, true)
	config.Get().Set(config.AliyunMarketPlaceAccessKeyId, "LTAI5tB8p9j6Ex2xVMU77EUA")
	config.Get().Set(config.AliyunMarketPlaceAccessKeySecret, "9w302a4CUGBg4T9QS7wQNFUKhbcYR5")
	config.Get().Set(config.AliyunMarketPlaceBillableItem, "Frequency")
	config.Get().Set(config.AliyunProductUnitPriceFactor, 1)
	config.Get().Set(config.AliyunMarketPlaceRegionId, "ap-southeast-1")
	config.Get().Set(config.AliyunMarketPlaceEndpoint, "marketplaceintl-dualstack.ap-southeast-1.aliyuncs.com")
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	assert.NoError(datainit.CreateMeasures(tenantID))

	mockCentral()
	paymenthandler.SetPaymentHandler(&impl.PaymentHandlerImpl{})

	paymentMethod, err := (&paymentsub.PaymentModel{}).CreatePaymentMethodAndActive(
		tenantID,
		&forms.PaymentMethodCreateQuery{
			PaymentType:           entity.PaymentType_AliCloud,
			SetUpPaymentCode:      true,
			PaymentCode:           testAliUid,
			BindedBillingConfigID: &billingConfigID,
		},
	)
	assert.NoError(err)

	assert.NoError((&entity.AliyunSubscriptionManagement{
		AliUid:       testAliUid,
		BizId:        "5000003370166",
		IsSubscribed: true,
		ProductCode:  "sgcmgj00036764",
	}).Create(db.GetDB()))

	handler := &impl.AliCloudPaymentHandlerImpl{}
	invoices := []forms.Invoice{{
		InvoiceID: 1,
		TenantID:  tenantID,
		Costs: forms.Costs{CategoryCosts: forms.CategoryCosts{Price: forms.Price{
			CostPrice: decimal.NewFromInt(1),
		}}},
	}}

	// Scenario 1: mock time near hour boundary → skip (return nil, nil)
	config.Get().Set(config.AliyunHourBoundarySafeMargin, "5m")
	dangerNow := time.Date(2026, 7, 10, 10, 56, 0, 0, time.UTC)
	timeutils.SetMockNow(dangerNow)
	result, err := handler.PayMultiInvoice(paymentMethod, nil, invoices)
	assert.Nil(err)
	assert.Nil(result)

	// Scenario 2: mock old time → Aliyun rejects GmtCreate as invalid
	config.Get().Set(config.AliyunHourBoundarySafeMargin, "1ms")
	oldNow := time.Date(2021, 11, 1, 1, 30, 0, 0, time.UTC)
	timeutils.SetMockNow(oldNow)
	result, err = handler.PayMultiInvoice(paymentMethod, nil, invoices)
	assert.Nil(result)
	assert.Error(err)
	assert.Contains(err.Error(), "GmtCreate parameter is invalid")

	// Scenario 3: real time → actually call aliyun API
	timeutils.SetNowFunc(nil)
	config.Get().Set(config.AliyunHourBoundarySafeMargin, "1ms")
	result, err = handler.PayMultiInvoice(paymentMethod, nil, invoices)
	fmt.Printf("aliyun API call result: result=%v, err=%v\n", result, err)
}

func (s *WorkerTestSuite) TestSendLakeCloudServiceFeeMetering_DelayGate() {
	assert := s.Assert()

	config.Get().Set(config.LakeCloudServiceFeeTenantList, []string{})
	config.Get().Set(config.LakeCloudServiceFeeDelayHours, 6)

	// 03:00 UTC — within delay window → should skip
	timeutils.SetNowFunc(func() time.Time {
		return time.Date(2026, 7, 22, 3, 0, 0, 0, time.UTC)
	})
	err := SendLakeCloudServiceFeeMetering()
	assert.NoError(err) // returns nil without running

	// 07:00 UTC — past delay window → should run (empty tenant list → no-op but passes gate)
	timeutils.SetNowFunc(func() time.Time {
		return time.Date(2026, 7, 22, 7, 0, 0, 0, time.UTC)
	})
	err = SendLakeCloudServiceFeeMetering()
	assert.NoError(err)

	timeutils.SetNowFunc(nil)
}
