package tests

import (
	"testing"
	"time"

	"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/eventbushandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/marketing/plan"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/pp"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
)

type OnDemandPlanSuite struct {
	tests.BillingTestSuite
}

func TestOnDemandPlanSuite(t *testing.T) {
	suite.Run(t, new(OnDemandPlanSuite))
}

func (suite *OnDemandPlanSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()
	assert := suite.Assert()

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

	eventbushandler.Get().(*tests.LocalEventBusHandlerImpl).CleanEvents()

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

	assert.NoError(err)
	config.Get().Set(config.OnDemandPackage, datainit.NoUsePackageId)
	config.Get().Set(config.EnableCache, true)
	cachehandler.InitWithLocal()
}

func (suite *OnDemandPlanSuite) TestEnableOnDemand() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

	model := &plan.TenantPlanModel{}

	planInfo, err := model.GetTenantOnDemandPlanInfo(tenantID, 1)
	assert.NoError(err)
	assert.Equal(planInfo.Status, entity.TenantPlanStatus_NotStarted)

	tenantPlan, err := model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_NewUser)

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

	planInfo, err = model.GetTenantOnDemandPlanInfo(tenantID, 1)
	assert.NoError(err)
	assert.NotZero(planInfo.BindedPackageID)
	assert.Equal(planInfo.Status, entity.TenantPlanStatus_Active)

	bindedPackagesInfo, err := (&billing.BindedPackageModel{}).GetAllTenantBindedPackage(tenantID)
	assert.NoError(err)
	assert.Len(bindedPackagesInfo, 1)
	assert.Equal(bindedPackagesInfo[0].BindedPackageID, planInfo.BindedPackageID)
	assert.EqualValues(bindedPackagesInfo[0].PackageID, config.Get().GetUint(config.OnDemandPackage))

	eh := eventbushandler.Get()
	events := eh.(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(plan.TenantPlanEventType)
	assert.Len(events, 1)
	assert.Equal(events[0].EventType, plan.TenantPlanEventType)
	eData := events[0].EventData.(*forms.TenantPlanEvent)
	assert.Equal(entity.TenantPlan_OnDemand, eData.ChangedPlan)
	assert.Equal(entity.TenantPlanStatus_Active, eData.ChangedStatus)

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

	planInfo, err = model.GetTenantOnDemandPlanInfo(tenantID, 1)
	assert.NoError(err)
	assert.NotZero(planInfo.BindedPackageID)
	assert.Equal(planInfo.Status, entity.TenantPlanStatus_Active)
	events = eh.(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(plan.TenantPlanEventType)
	assert.Len(events, 1)
	assert.Equal(events[0].EventType, plan.TenantPlanEventType)

	tenantPlan, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_OnDemand)
}

func (suite *OnDemandPlanSuite) TestEnableOnDemandConcurrently() {
	assert := suite.Assert()
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	model := &plan.TenantPlanModel{}

	planInfo, err := model.GetTenantOnDemandPlanInfo(tenantID, 1)
	assert.NoError(err)
	assert.Equal(planInfo.Status, entity.TenantPlanStatus_NotStarted)

	tenantPlan, err := model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_NewUser)

	enableTenantPlan := func(ch chan struct{}) {
		err = model.EnableTenantOnDemandPlan(tenantID)
		assert.NoError(err)
		close(ch)
	}
	concurrency := 10
	finishChan := make([]chan struct{}, concurrency)
	for i := 0; i != concurrency; i++ {
		finishChan[i] = make(chan struct{})
		go enableTenantPlan(finishChan[i])
	}
	for i := 0; i != concurrency; i++ {
		select {
		case <-finishChan[i]:
		case <-time.After(time.Second):
			panic("timeout")
		}
	}

	planInfo, err = model.GetTenantOnDemandPlanInfo(tenantID, 1)
	assert.NoError(err)
	assert.NotZero(planInfo.BindedPackageID)
	assert.Equal(planInfo.Status, entity.TenantPlanStatus_Active)

	onDemandPackageID := config.Get().GetUint(config.OnDemandPackage)
	bindedPackages, err := (&billing.BindedPackageModel{}).GetTenantPackageBindedPackage(tenantID, onDemandPackageID)
	assert.Len(bindedPackages, 1)

	tenantPlan, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_OnDemand)
}

func (suite *OnDemandPlanSuite) TestDisableOndemandPackage() {
	assert := suite.Assert()
	var err error
	config.Get().Set(config.EnableCache, true)
	model := &plan.TenantPlanModel{}
	measureModel := &billing.MeasureModel{}
	tenantID := uint(1)
	datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)

	payment, err := (&paymentsub.PaymentModel{}).GetActivePaymentMethod(tenantID, 1)
	assert.NoError(err)
	err = entity.NewPaymentMethodQuerySet(db.GetDB()).IDEq(payment.PaymentMethodID).GetUpdater().SetPayable(false).SetUnpayableAt(nil).SetPaymentType(entity.PaymentType_GCP).Update()
	assert.NoError(err)

	tenantPlan, err := model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_NewUser)

	err = model.ProcessPayableChange(tenantID)
	assert.NoError(err)
	tenantPlan, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_NewUser)

	costQuery := &forms.UnitCosts{Unit: datainit.TestUnit, Cost: 1, CostTimeStamp: timeutils.GetNow().Unix()}
	cost, err := measureModel.CostUnits(tenantID, costQuery)
	pp.Print(cost)
	assert.NoError(err)
	assert.Equal(cost.ConsumedSource, entity.ConsumedSource_None)

	err = entity.NewPaymentMethodQuerySet(db.GetDB()).IDEq(payment.PaymentMethodID).GetUpdater().SetPayable(true).Update()
	assert.NoError(err)

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

	pkg, err := plan.GetOnDemandBindedPackage(tenantID, 1)
	assert.NoError(err)
	assert.False(pkg.Internal)
	cost, err = measureModel.CostUnits(tenantID, costQuery)
	assert.NoError(err)
	assert.False(cost.Internal)
	tenantPlan, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_OnDemand)

	err = entity.NewPaymentMethodQuerySet(db.GetDB()).IDEq(payment.PaymentMethodID).GetUpdater().SetPayable(false).SetUnpayableAt(timeutils.GetNowAddr()).Update()
	assert.NoError(err)

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

	pkg, err = plan.GetOnDemandBindedPackage(tenantID, 1)
	assert.NoError(err)
	assert.False(pkg.Internal)
	err = model.ProcessPayableChange(tenantID)
	assert.NoError(err)
	pkg, err = plan.GetOnDemandBindedPackage(tenantID, 1)
	assert.NoError(err)
	assert.False(pkg.Internal)
	cost, err = measureModel.CostUnits(tenantID, costQuery)
	assert.NoError(err)
	assert.False(cost.Internal)
	tenantPlan, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_NewUser)

	err = entity.NewPaymentMethodQuerySet(db.GetDB()).IDEq(payment.PaymentMethodID).GetUpdater().SetPayable(true).SetUnpayableAt(nil).Update()
	assert.NoError(err)

	err = model.ProcessPayableChange(tenantID)
	assert.NoError(err)
	pkg, err = plan.GetOnDemandBindedPackage(tenantID, 1)
	assert.NoError(err)
	assert.False(pkg.Internal)
	cost, err = measureModel.CostUnits(tenantID, costQuery)
	assert.NoError(err)
	assert.False(cost.Internal)
	tenantPlan, err = model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_OnDemand)
}

func (suite *OnDemandPlanSuite) TestNoBillingConfigTenantOnDemandPlan() {
	suite.T().Skip("must have billing config")
	assert := suite.Assert()
	tenantID := uint(1)
	//datainit.CreateBillingConfig(tenantID, entity.BillingConfigType_DirectCustomer)
	model := &plan.TenantPlanModel{}

	planInfo, err := model.GetTenantOnDemandPlanInfo(tenantID, 1)
	assert.NoError(err)
	assert.Equal(planInfo.Status, entity.TenantPlanStatus_NotStarted)

	tenantPlan, err := model.GetTenantEffectivePlan(tenantID)
	assert.NoError(err)
	assert.Equal(tenantPlan.EffectivePlan, entity.TenantPlan_NewUser)
	assert.Equal(tenantPlan.OnDemandPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
}
