package tests

import (
	"fmt"
	"testing"
	"time"

	"github.com/pingcap/dbaas/billing2/handler/centralhandler"

	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/eventbushandler"
	"github.com/pingcap/dbaas/billing2/handler/salesforcehandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/model/marketing/plan"
	"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/mock"
	"github.com/stretchr/testify/suite"
)

type TenantPlanSuite struct {
	tests.BillingTestSuite
}

func TestTenantPlanSuite(t *testing.T) {
	suite.Run(t, new(TenantPlanSuite))
}

func (suite *TenantPlanSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	assert := suite.Assert()
	timeutils.GetNowFunc = func() time.Time {
		return datainit.BillingDate
	}
	packageModel := &billing.PackageModel{}

	packageInfo, err := packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices:    forms.UnitPrices{},
			Discount:      decimal.NewFromInt(1),
			CreditsEnable: true,
			CreditsOnly:   true,
		})
	assert.NoError(err)
	config.Get().Set(config.POCPackage, packageInfo.PackageID)
	config.Get().Set(config.CreditsPricePackage, packageInfo.PackageID)
	generalUnitFilter, err := (&unit_filter.Model{}).CreateUnitFilter(&forms.UnitFilter{
		UnitFilterSlim: forms.UnitFilterSlim{Name: "General", Description: "General"},
		General:        true,
	})
	assert.NoError(err)
	config.Get().Set(config.GeneralCreditTypeID, generalUnitFilter.FilterID)
}

func (suite *TenantPlanSuite) TestTenantPlanSuite() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	billingConfigID := uint(1)

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

	model := &plan.TenantPlanModel{}
	info, err := model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_NewUser)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_NotStarted)

	packageModel := &billing.PackageModel{}

	packageInfo, err := packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices:    forms.UnitPrices{},
			Discount:      decimal.NewFromInt(1),
			CreditsEnable: true,
			CreditsOnly:   true,
		})
	assert.NoError(err)
	config.Get().Set(config.POCPackage, packageInfo.PackageID)
	config.Get().Set(config.CreditsPricePackage, packageInfo.PackageID)

	packageInfo, err = packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices:           forms.UnitPrices{},
			Discount:             decimal.NewFromInt(1),
			TenantMaxBindedTimes: 1,
		})
	assert.NoError(err)
	config.Get().Set(config.DevTierPackage, packageInfo.PackageID)

	packageInfo, err = packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices:           forms.UnitPrices{},
			Discount:             decimal.NewFromInt(1),
			TenantMaxBindedTimes: 1,
		})
	assert.NoError(err)
	config.Get().Set(config.OnDemandPackage, packageInfo.PackageID)

	err = model.EnableTenantDevTierPlan(tenantID)
	assert.NoError(err)

	info, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_DevTier)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
	creditsModel := &marketing.CreditsPackageModel{}
	creditsPackage, err := creditsModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(1),
		UnitFilters:           []*forms.UnitFilterSlim{{TypeID: config.Get().GetUint(config.GeneralCreditTypeID)}},
		Usage:                 entity.CreditsPackageUsage_POC,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	err = model.EnableTenantPOCPlanAfterCreditsOpened(tenantID)
	assert.NoError(err)

	info, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_POC)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_NotStarted)

	err = creditsModel.CloseCreditsPackage(creditsPackage.CreditsPackageID, "")
	assert.NoError(err)
	cPackage, _ := creditsModel.GetCreditsPackage(creditsPackage.CreditsPackageID)
	mockCentral := &MockPOCCentralHandlerImpl{}
	centralhandler.SetCentralHandler(mockCentral)
	mockCentral.On("SendTenantAdminEmailOnce", tenantID, mock.Anything, plan.TemplateID_PocCreditsExhaust_Inner, mock.Anything).Return(nil)
	mockCentral.On("SendTenantManagerEmailOnce", tenantID, mock.Anything, plan.TemplateID_PocCreditsExhaust_Inner, mock.Anything).Return(nil)

	err = model.DisableTenantPOCPlanAfterCreditsExhausted(cPackage.CreditsPackageID)
	assert.NoError(err)

	info, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_POC)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_Closing)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_NotStarted)

	creditsPackage, err = creditsModel.CreateCreditsPackage(tenantID, &forms.CreditsPackage{
		StartDate:             datainit.BillingDate,
		ExpiredDate:           datainit.BillingDate.AddDate(0, 1, 0),
		TotalCredits:          decimal.NewFromInt(1),
		UnitFilters:           []*forms.UnitFilterSlim{{FilterID: config.Get().GetUint(config.GeneralCreditTypeID)}},
		Usage:                 entity.CreditsPackageUsage_POC,
		BindedBillingConfigID: &billingConfigID,
	})
	assert.NoError(err)
	err = model.EnableTenantPOCPlanAfterCreditsOpened(tenantID)
	assert.NoError(err)
	info, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_POC)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_NotStarted)

	err = model.EnableTenantOnDemandPlan(tenantID)
	assert.NoError(err)
	info, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_OnDemand)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_Active)

	err = creditsModel.CloseCreditsPackage(creditsPackage.CreditsPackageID, "")
	assert.NoError(err)
	cPackage, _ = creditsModel.GetCreditsPackage(creditsPackage.CreditsPackageID)
	err = model.DisableTenantPOCPlanAfterCreditsExhausted(cPackage.CreditsPackageID)
	assert.NoError(err)

	info, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(info.EffectivePlan, entity.TenantPlan_OnDemand)
	assert.Equal(info.DevTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.Equal(info.POCPlanInfo.Status, entity.TenantPlanStatus_Closed)
	assert.Equal(info.OnDemandPlanInfo.Status, entity.TenantPlanStatus_Active)
}

type MockTenantPlanSalesforceImpl struct {
	salesforcehandler.SalesforceHandler
	mock.Mock
	accountCh chan salesforcehandler.Account
}

func (m *MockTenantPlanSalesforceImpl) UpdateAccount(account salesforcehandler.Account) error {
	m.Called(account)
	if m.accountCh != nil {
		select {
		case m.accountCh <- account:
		default:
		}
	}
	return nil
}

func (suite *TenantPlanSuite) TestUpdateSalesforce() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	model := &plan.TenantPlanModel{}
	mocksf := &MockTenantPlanSalesforceImpl{}
	salesforcehandler.Set(mocksf)

	tests.MockCentralHandler()
	mocksf.On("UpdateAccount", mock.Anything).Return(nil)

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

	timeutils.SetMockNow(datainit.BillingDate)

	err := model.UpdateSalesforceAfterPlanChanged(tenantID)
	assert.NoError(err)

	assert.Len(mocksf.Calls, 1)
	account := mocksf.Calls[0].Arguments[0].(salesforcehandler.Account)
	assert.Equal(account.PortalID, fmt.Sprint(tenantID))
	assert.Equal(account.Plan, entity.TenantPlan_NewUser)
	assert.Equal(account.BindCreditCardAt, "2021-11-01 00:00:00.000Z")
}

func (suite *TenantPlanSuite) TestUpdateSalesforceOnDemand() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	eb, err := tests.NewLocalEventBusHandler()
	assert.NoError(err)
	err = eventbushandler.Get().Close()
	assert.NoError(err)
	eventbushandler.Set(eb)
	assert.NoError(err)
	model := &plan.TenantPlanModel{}
	err = model.RegisterPlanEventHandler()
	assert.NoError(err)
	err = eb.Run()
	assert.NoError(err)
	err = datainit.CreatePaymentMock(tenantID)
	assert.NoError(err)
	packageModel := &billing.PackageModel{}

	packageInfo, err := packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices:           forms.UnitPrices{},
			Discount:             decimal.NewFromInt(1),
			TenantMaxBindedTimes: 1,
		})
	assert.NoError(err)
	config.Get().Set(config.OnDemandPackage, packageInfo.PackageID)

	mocksf := &MockTenantPlanSalesforceImpl{
		accountCh: make(chan salesforcehandler.Account, 16),
	}
	salesforcehandler.Set(mocksf)

	tests.MockCentralHandler()
	mocksf.On("UpdateAccount", mock.Anything).Return(nil)

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

	timeutils.SetMockNow(datainit.BillingDate)
	err = model.EnableTenantOnDemandPlan(tenantID)
	assert.NoError(err)

	// The event bus replays tenant_plan events published by earlier tests in this
	// package, so UpdateAccount may be invoked more than once with stale plans.
	// Wait for the on_demand update instead of asserting on the number of calls.
	var account salesforcehandler.Account
	found := false
	for !found {
		select {
		case account = <-mocksf.accountCh:
			found = account.Plan == entity.TenantPlan_OnDemand
		case <-time.After(2 * time.Second):
			assert.FailNow("timed out waiting for salesforce UpdateAccount call with on_demand plan")
		}
	}
	assert.Equal(account.PortalID, fmt.Sprint(tenantID))
	assert.Equal(account.BindCreditCardAt, "2021-11-01 00:00:00.000Z")
}
