package workers

import (
	"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/handler/cachehandler"
	"github.com/pingcap/dbaas/billing2/handler/centralhandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/payment"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
)

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

type InvoiceWorkerSuite struct {
	tests.BillingTestSuite
}

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

type MockCentralHandlerImpl struct{}

func (*MockCentralHandlerImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	return &forms.Tenant{
		TenantID: tenantID,
		Plan:     "on_demand",
	}, nil
}

func (*MockCentralHandlerImpl) GetAllTenants() ([]forms.Tenant, error) {
	return []forms.Tenant{
		{
			TenantID: 1,
			Plan:     "on_demand",
		},
	}, nil
}

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

func (s *InvoiceWorkerSuite) TestCreateMontlyInvoice() {
	assert := s.Assert()

	viper.Set(config.EnableInvoiceWorker, true)

	invoiceDay := viper.GetInt(config.InvoiceDay)

	now := datainit.BillingDate.AddDate(0, 1, invoiceDay)

	getNowDate = func() time.Time {
		return now
	}
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)

	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})

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

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

	tests.MockCentralHandler()
	viper.Set(config.AWSPaymentEnable, false)
	_, err = (&paymentsub.PaymentModel{}).CreatePaymentMethod(
		tenantID,
		&forms.PaymentMethod{
			PaymentType: "aws",
		},
	)
	if err != nil {
		return err
	}

	measureModel := &billing.MeasureModel{}
	_, err = measureModel.CostUnits(tenantID, &forms.UnitCosts{
		CostTimeStamp: datainit.BillingDate.Unix(),
		Unit:          datainit.TestUnit,
		Cost:          1,
	})
	if err != nil {
		return err
	}
	return nil
}

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

	viper.Set(config.EnableInvoiceWorker, true)

	now := datainit.BillingDate.Add(time.Hour * 24)

	getNowDate = func() time.Time {
		return now
	}
	err := createMarketplaceMeasures(uint(1))
	assert.NoError(err)

	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})

	CreateInvoiceWorkerFunc()
	invoiceModel := &payment.InvoiceModel{}
	invoices, err := invoiceModel.ListAllInvoices(1)
	assert.NoError(err)
	assert.Len(invoices, 1)
	invoice := invoices[0]
	assert.Equal(invoice.StartDate, datainit.BillingDate)
}

func (s *InvoiceWorkerSuite) TestCreateMontlyInvoiceDisabledConfig() {
	assert := s.Assert()
	viper.Set(config.EnableInvoiceWorker, false)
	now := datainit.BillingDate.AddDate(0, 1, 0)
	getNowDate = func() time.Time {
		return now
	}
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)

	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})

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

type MockPaymentProvider struct {
	mock.Mock
	paymenthandler.PaymentProvider
}

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

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

type MockPaymentHandler struct {
	Provider *MockPaymentProvider
}

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

func (s *InvoiceWorkerSuite) TestSyncPayable() {
	assert := s.Assert()
	viper.Set(config.EnableSyncPayableWorker, true)
	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})
	m := &MockPaymentProvider{}
	handler := &MockPaymentHandler{Provider: m}
	paymenthandler.SetPaymentHandler(handler)
	m.On("Payable").Return(false).Once()

	paymentModel := &paymentsub.PaymentModel{}
	_, err := paymentModel.CreatePaymentMethod(1, &forms.PaymentMethod{
		PaymentType: entity.PaymentType_STRIPE,
	})
	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 *InvoiceWorkerSuite) TestCalculateCache() {
	assert := s.Assert()
	cachehandler.InitWithLocal()
	viper.Set(config.EnableCache, true)
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	now := datainit.BillingDate.Add(time.Hour * 24)

	getNowDate = func() time.Time {
		return now
	}
	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})

	CalculateCache()

	assert.True(cachehandler.Get().Get("GetMonthlyCost", nil, 1, now.Year(), int(now.Month())))
}

func (s *InvoiceWorkerSuite) TestRemoveUnusedCosts() {
	assert := s.Assert()
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	now := datainit.BillingDate.Add(time.Hour * 24 * viper.GetDuration(config.RemoveUnusedCostsDays))

	getNowDate = func() time.Time {
		return now
	}
	measureModel := billing.MeasureModel{}

	_, err = measureModel.CostUnits(uint(1), &forms.UnitCosts{
		CostTimeStamp: datainit.BillingDate.Unix(),
		Unit:          "not_exists",
		Cost:          1,
	})
	assert.Error(err)

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

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

func (s *InvoiceWorkerSuite) TestMergeSmallCosts() {
	assert := s.Assert()
	err := datainit.CreateMeasures(uint(1))
	assert.NoError(err)
	now := datainit.BillingDate.Add(time.Hour * 24 * viper.GetDuration(config.MergeSmallCostsDays))

	getNowDate = 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)
	MergeSmallCosts()
	cnt, err = entity.NewUserUnitCostQuerySet(db.GetDB()).StatusEq(entity.UserUnitCostStatus_DONE).Count()
	assert.NoError(err)
	assert.EqualValues(cnt, 1)
}
