package tests

import (
	"errors"
	"github.com/tidbcloud/account/idl/pbgen/proto/account"
	"testing"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/constant"
	"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/eventbushandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler"
	"github.com/pingcap/dbaas/billing2/handler/paymenthandler/impl"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/billingconfig/event_handler"
	"github.com/pingcap/dbaas/billing2/model/marketing"
	"github.com/pingcap/dbaas/billing2/utils/concurrency"
	"github.com/pingcap/dbaas/billing2/utils/tests"
	"github.com/pingcap/dbaas/billing2/utils/tests/datainit"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/pingcap/dbaas/billing2/workers"
	"github.com/shopspring/decimal"
	"github.com/stretchr/testify/suite"
)

var billingConfigModel billingconfig.BillingConfigModel

type BillingConfigTestSuite struct {
	tests.BillingTestSuite
}

func TestBillingConfig(t *testing.T) {
	suite.Run(t, new(BillingConfigTestSuite))
}

type MockCentralHandlerImpl struct {
	centralhandler.CentralHandler
}

func (*MockCentralHandlerImpl) GetTenant(tenantID uint) (*forms.Tenant, error) {
	if tenantID == 211 {
		return &forms.Tenant{
			TenantID:                  tenantID,
			Status:                    centralhandler.TenantStatusActive,
			IsMspCustomer:             true,
			ThirdPartyAccountProvider: constant.MSPAccountProvider,
			ThirdPartyAccountCode:     "333",
		}, nil
	}
	if tenantID == 222 {
		return &forms.Tenant{
			TenantID:                  tenantID,
			Status:                    centralhandler.TenantStatusActive,
			IsMspCustomer:             !timeutils.GetNow().Before(timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 1, 0)),
			ThirdPartyAccountProvider: constant.MSPAccountProvider,
			ThirdPartyAccountCode:     "333",
		}, nil
	}
	if tenantID == 555 {
		return &forms.Tenant{
			TenantID:       tenantID,
			Status:         centralhandler.TenantStatusActive,
			IsCPPOReseller: !timeutils.GetNow().Before(timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 1, 0)),
		}, nil
	}
	if tenantID == 566 {
		return &forms.Tenant{
			TenantID:       tenantID,
			Status:         centralhandler.TenantStatusActive,
			IsCPPOCustomer: !timeutils.GetNow().Before(timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 1, 0)),
		}, nil
	}
	return &forms.Tenant{
		TenantID: tenantID,
		Status:   centralhandler.TenantStatusActive,
	}, nil
}

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

func (i *MockCentralHandlerImpl) GetMspCustomerScheduleInfo(customerID uint) (*forms.MspCustomerScheduleInfo, error) {
	if customerID == 222 {
		return &forms.MspCustomerScheduleInfo{
			MspTenantID:      333,
			CustomerTenantID: 222,
			Status:           "pending",
			EffectedAt:       timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 1, 0),
		}, nil
	}
	if customerID == 227 {
		//if timeutils.GetNow().Before(timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 0, 3)) {
		return &forms.MspCustomerScheduleInfo{
			MspTenantID:      333,
			CustomerTenantID: 222,
			Status:           "pending",
			EffectedAt:       timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 1, 0),
		}, nil
		//}
	}
	return nil, billing_errors.ObjectNotExistsError
}

func (i *MockCentralHandlerImpl) GetPendingCustomerScheduleInfo(tenantID uint, partnerType account.PartnerType) ([]*forms.PartnerCustomerScheduleInfo, error) {
	switch partnerType {
	case account.PartnerType_PARTNER_TYPE_MSP:
		result, err := i.GetMspCustomerScheduleInfo(tenantID)
		if err != nil {
			return nil, err
		}
		return []*forms.PartnerCustomerScheduleInfo{
			{PartnerTenantID: result.MspTenantID, CustomerTenantID: result.CustomerTenantID, Status: result.Status, EffectedAt: result.EffectedAt},
		}, nil
	}
	return nil, nil
}

func (s *BillingConfigTestSuite) TestBillingConfig() {
	centralhandler.SetCentralHandler(&MockCentralHandlerImpl{})
	paymenthandler.SetPaymentHandler(&impl.PaymentHandlerImpl{})
	assert := s.Assert()
	pool, err := concurrency.NewPool(8)
	assert.NoError(err)
	timeutils.SetMockNow(datainit.BillingDate)
	pool.SetNow(datainit.BillingDate)
	pool.Start()
	for i := 0; i != 2; i++ {
		pool.Submit(func() {
			err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
				OrgId:        uint64(1),
				OrgEventType: constant.TenantEventTypeCreate,
			})
			assert.NoError(err)
		})
		pool.Submit(func() {
			err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
				OrgId:        uint64(211),
				OrgEventType: constant.TenantEventTypeCreate,
			})
			assert.NoError(err)
		})
		pool.Submit(func() {
			err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
				OrgId:        uint64(222),
				OrgEventType: constant.TenantEventTypeCreate,
			})
			assert.NoError(err)
		})
		pool.Submit(func() {
			err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
				OrgId:        uint64(222),
				OrgEventType: constant.TenantEventTypeSchedule,
			})
			assert.NoError(err)
		})
	}
	pool.Wait()
	eb := eventbushandler.Get().(*tests.LocalEventBusHandlerImpl)
	var events []*eventbushandler.Event
	events = eb.GetAllTxEvents(constant.BillingConfigSwitchedEvent)
	assert.Len(events, 3)
	b1, err := billingConfigModel.GetActiveBillingConfig(1)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_DirectCustomer, b1.Type)
	b2, err := billingConfigModel.GetActiveBillingConfig(211)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_MSPCustomer, b2.Type)
	b3, err := billingConfigModel.GetActiveBillingConfig(222)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_DirectCustomer, b3.Type)

	err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
		OrgId:        uint64(227),
		OrgEventType: constant.TenantEventTypeCreate,
	})
	assert.NoError(err)
	err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
		OrgId:        uint64(227),
		OrgEventType: constant.TenantEventTypeSchedule,
	})
	assert.NoError(err)

	err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
		OrgId:        uint64(566),
		OrgEventType: constant.TenantEventTypeCreate,
	})
	assert.NoError(err)
	err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
		OrgId:        uint64(555),
		OrgEventType: constant.TenantEventTypeCreate,
	})
	assert.NoError(err)
	_, err = billingConfigModel.CreateBillingConfigAndScheduled(forms.BillingConfig{
		TenantID:        uint(566),
		StartActiveTime: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 1, 0)),
		EndActiveTime:   nil,
		Type:            entity.BillingConfigType_MarketplaceResellerManagedCustomer,
	})
	assert.NoError(err)

	_, err = billingConfigModel.CreateBillingConfigAndScheduled(forms.BillingConfig{
		TenantID:        uint(555),
		StartActiveTime: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 1, 0)),
		EndActiveTime:   nil,
		Type:            entity.BillingConfigType_MarketplaceReseller,
		ManagedTenantID: 566,
	})
	assert.NoError(err)
	billingConfigs, err := billingConfigModel.GetUseBillingConfigID(555)
	assert.NoError(err)
	assert.Len(billingConfigs, 1)
	billingConfigs, err = billingConfigModel.GetManagingBillingConfigEntities(555)
	assert.NoError(err)
	assert.Len(billingConfigs, 0)

	timeutils.SetMockNow(timeutils.GetMonthDate(datainit.BillingDate).AddDate(0, 0, 3).Add(time.Second))
	err = (&event_handler.BillingConfigEventHandler{}).CreateOrUpdateBillingConfig(&forms.OrgEventData{
		OrgId:        uint64(227),
		OrgEventType: constant.TenantEventTypeScheduleCancel,
	})
	assert.NoError(err)
	_, err = billingConfigModel.CreateBillingConfigAndScheduled(forms.BillingConfig{
		TenantID:        uint(227),
		StartActiveTime: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 1, 0)),
		EndActiveTime:   nil,
		Type:            entity.BillingConfigType_MSPCustomer,
	})
	assert.Error(err)
	assert.True(errors.Is(err, billing_errors.ObjectAlreadyExists))
	timeutils.SetMockNow(datainit.BillingDate.AddDate(0, 1, 0))
	workers.BillingConfigScheduledExecution()
	b3, err = billingConfigModel.GetActiveBillingConfig(222)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_MSPCustomer, b3.Type)

	b4, err := billingConfigModel.GetActiveBillingConfig(227)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_DirectCustomer, b4.Type)

	b5, err := billingConfigModel.GetActiveBillingConfig(566)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_MarketplaceResellerManagedCustomer, b5.Type)

	billingConfigs, err = billingConfigModel.GetManagingBillingConfigEntities(555)
	assert.NoError(err)
	assert.Len(billingConfigs, 1)
	_, err = billingConfigModel.CreateBillingConfigAndScheduled(forms.BillingConfig{
		TenantID:        uint(227),
		StartActiveTime: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 2, 0)),
		EndActiveTime:   nil,
		Type:            entity.BillingConfigType_MSPCustomer,
	})
	assert.NoError(err)
}

func (s *BillingConfigTestSuite) TestCreateAWSCPPOBillingConfig() {
	assert := s.Assert()
	timeutils.SetMockNow(datainit.BillingDate)
	supportPlanModel := &marketing.SupportPlanModel{}
	info, err := supportPlanModel.CreateSupportPlan(&forms.SupportPlan{
		FixedChargePrice:       decimal.NewFromInt(49),
		SupportPricePercentage: decimal.RequireFromString("0.2"),
		Type:                   entity.SupportPlanType_PercentagePrice,
		Public:                 false,
		Level:                  entity.SupportPlanLevel_Standard,
		IsSupportFreeTrial:     false,
	})
	assert.NoError(err)
	var customerTenantID uint = 10
	var resellerTenantID uint = 20
	_, err = billingConfigModel.CreateBillingConfigAndActive(forms.BillingConfig{TenantID: customerTenantID, Type: entity.BillingConfigType_DirectCustomer})
	assert.NoError(err)
	customerDirectBillingConfigID, err := billingConfigModel.GetActiveBillingConfigID(customerTenantID)
	assert.NoError(err)
	_, err = billingConfigModel.CreateBillingConfigAndActive(forms.BillingConfig{TenantID: resellerTenantID, Type: entity.BillingConfigType_DirectCustomer})
	assert.NoError(err)
	resellerDirectBillingConfigID, err := billingConfigModel.GetActiveBillingConfigID(resellerTenantID)
	assert.NoError(err)

	assert.NoError(err)
	cppoCreateQuery := forms.CPPOBillingConfig{
		MarketplaceResellerBillingConfig: forms.MarketplaceResellerBillingConfig{
			TenantID:      customerTenantID,
			EffectiveDate: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(0, 0, 10)),
			PaymentCode:   "ADCXA12345",
			ResellerId:    resellerTenantID,
			SupportPlanInfo: &forms.SupportPlanInput{
				SupportPlan: *info,
				ExpiredDate: timeutils.GetTimeAddr(datainit.BillingDate.AddDate(1, 0, 0)),
			},
		},
		PaymentSubCode: "222222",
	}
	customerBillingConfigID, paymentBillingConfigID, err := billingConfigModel.CreateMarketplaceResellerBillingConfig(cppoCreateQuery.MarketplaceResellerBillingConfig)
	assert.NoError(err)
	customerBillingConfig, err := billingConfigModel.GetBillingConfig(customerBillingConfigID)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_MarketplaceResellerManagedCustomer, customerBillingConfig.Type)
	assert.Equal(entity.BillingConfigStatus_Scheduled, customerBillingConfig.Status)
	assert.Equal(cppoCreateQuery.EffectiveDate, customerBillingConfig.StartActiveTime)
	resellerBillingConfig, err := billingConfigModel.GetBillingConfig(paymentBillingConfigID)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigType_MarketplaceReseller, resellerBillingConfig.Type)
	assert.Equal(entity.BillingConfigStatus_Scheduled, resellerBillingConfig.Status)
	assert.Equal(cppoCreateQuery.EffectiveDate, resellerBillingConfig.StartActiveTime)
	assert.Equal(customerTenantID, resellerBillingConfig.ManagedTenantID)

	timeutils.SetMockNow(cppoCreateQuery.EffectiveDate.Add(-time.Second * 60))
	workers.BillingConfigScheduledExecution()
	customerDirectBillingConfig, err := billingConfigModel.GetBillingConfig(customerDirectBillingConfigID)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigStatus_Closing, customerDirectBillingConfig.Status)
	customerBillingConfig, err = billingConfigModel.GetBillingConfig(customerBillingConfigID)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigStatus_Active, customerBillingConfig.Status)
	resellerBillingConfig, err = billingConfigModel.GetBillingConfig(paymentBillingConfigID)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigStatus_Managing, resellerBillingConfig.Status)
	resellerDirectBillingConfig, err := billingConfigModel.GetBillingConfig(resellerDirectBillingConfigID)
	assert.NoError(err)
	assert.Equal(entity.BillingConfigStatus_Active, resellerDirectBillingConfig.Status)
}
