package tests

import (
	"testing"
	"time"

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

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

type DevTierPlanSuite struct {
	tests.BillingTestSuite
}

func TestDevTierPlanSuite(t *testing.T) {
	suite.Run(t, new(DevTierPlanSuite))
}

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 (suite *DevTierPlanSuite) SetupTest() {
	suite.BillingTestSuite.SetupTest()

	assert := suite.Assert()
	_, err := (&billing.UnitModel{}).CreateUnit(&forms.UnitInfo{
		UnitKey: datainit.TestUnit,
	})
	assert.NoError(err)
	mockCentral()

	packageModel := &billing.PackageModel{}
	info, err := packageModel.CreatePackage(
		&forms.PackageInfo{
			UnitPrices: forms.UnitPrices{
				datainit.TestUnit: {Price: decimal.NewFromInt(1)},
			},
			Discount:             decimal.NewFromInt(1),
			CreditsOnly:          true,
			CreditsEnable:        true,
			TenantMaxBindedTimes: 1,
		})
	assert.NoError(err)
	config.Get().Set(config.DevTierPackage, info.PackageID)
}

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

	model := &plan.TenantPlanModel{}

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

	bindedPackages, err := (&billing.BindedPackageModel{}).GetAllTenantBindedPackage(tenantID)
	assert.NoError(err)
	assert.Len(bindedPackages, 1)
	assert.Equal(bindedPackages[0].PackageID, config.Get().GetUint(config.DevTierPackage))

	devTierPlanInfo, err := model.GetTenantDevTierPlan(tenantID)
	assert.NoError(err)
	assert.Equal(devTierPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
	assert.NotZero(devTierPlanInfo.BindedPackageID)

	err = model.PrepareDevTier(tenantID)
	assert.NoError(err)
}

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

	model := &plan.TenantPlanModel{}
	r := make(chan error, 10)

	for i := 0; i < 10; i++ {
		go func() {
			var err error = nil
			for j := 0; j <= 3; j++ {
				err = model.PrepareDevTier(tenantID)
				if err == nil {
					break
				}
				time.Sleep(time.Second)
			}
			r <- err
		}()
	}

	for i := 0; i < 10; i++ {
		err := <-r
		assert.NoError(err)
	}

	// for now conrreunt bind may bind more than max binded limit package.
	// bindedPackages, err := (&billing.BindedPackageModel{}).GetAllTenantBindedPackage(tenantID)
	// assert.NoError(err)
	// assert.Len(bindedPackages, 1)
	// assert.Equal(bindedPackages[0].PackageID, config.Get().GetUint(config.DevTierPackage))

	devTierPlanInfo, err := model.GetTenantDevTierPlan(tenantID)
	assert.NoError(err)
	assert.Equal(devTierPlanInfo.Status, entity.TenantPlanStatus_NotStarted)
	assert.NotZero(devTierPlanInfo.BindedPackageID)

	err = model.PrepareDevTier(tenantID)
	assert.NoError(err)
}

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

	model := &plan.TenantPlanModel{}
	err := model.EnableTenantDevTierPlan(tenantID)
	pp.Print(err)
	assert.NoError(err)

	devTierPlanInfo, err := model.GetTenantDevTierPlan(tenantID)
	assert.NoError(err)
	assert.Equal(devTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.NotZero(devTierPlanInfo.BindedPackageID)
}

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

	model := &plan.TenantPlanModel{}

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

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

	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_DevTier, eData.ChangedPlan)
	assert.Equal(entity.TenantPlanStatus_Active, eData.ChangedStatus)

	devTierPlanInfo, err := model.GetTenantDevTierPlan(tenantID)
	assert.NoError(err)
	assert.Equal(devTierPlanInfo.Status, entity.TenantPlanStatus_Active)
	assert.NotZero(devTierPlanInfo.BindedPackageID)

	err = model.EnableTenantDevTierPlan(tenantID)
	assert.NoError(err)
	events = eh.(*tests.LocalEventBusHandlerImpl).GetAllTxEvents(plan.TenantPlanEventType)
	assert.Len(events, 1)
}
