package billingconfig

import (
	"errors"
	"fmt"
	"slices"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"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/eventbushandler"
	"github.com/pingcap/dbaas/billing2/model/aliyunsubscriptionmngt"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type BillingConfigModel struct{}

func billingConfigFromEntity(ent *entity.BillingConfig) *forms.BillingConfig {
	return &forms.BillingConfig{
		BillingConfigID: ent.ID,
		Type:            ent.Type,
		Status:          ent.Status,
		TenantID:        ent.TenantID,
		ManagedTenantID: ent.ManagedTenantID,
		StartActiveTime: &ent.StartActiveTime,
		EndActiveTime:   ent.EndActiveTime,
		UpdatedAt:       ent.UpdatedAt,
		CreatedAt:       ent.CreatedAt,

		BindSupportPlanOnEnabled: ent.BindSupportPlanOnEnabled,
		SupportPlanID:            ent.SupportPlanID,
		SupportPlanExpiredDate:   ent.SupportPlanExpiredDate,
	}
}

func (model *BillingConfigModel) GetActiveBillingConfigIDWithCache(tenantID uint) (uint, error) {
	cache := cachehandler.Get()
	var billingConfigID uint
	found := cache.Get("GetActiveBillingConfigID", &billingConfigID, tenantID)
	if found {
		return billingConfigID, nil
	}
	result, err := model.GetActiveBillingConfigID(tenantID)
	if err == nil {
		cache.Set("GetActiveBillingConfigID", result, tenantID)
	}
	return result, err
}

func (model *BillingConfigModel) GetActiveBillingConfigIDWithTx(tx *gorm.DB, tenantID uint) (uint, error) {
	bcfg, err := model.GetActiveBillingConfigWithTx(tx, tenantID)
	if err != nil {
		return 0, err
	}
	return bcfg.BillingConfigID, nil
}

func (model *BillingConfigModel) GetActiveBillingConfigID(tenantID uint) (uint, error) {
	bcfg, err := model.GetActiveBillingConfig(tenantID)
	if err != nil {
		return 0, err
	}
	return bcfg.BillingConfigID, nil
}

func (model *BillingConfigModel) GetActiveBillingConfigWithTx(tx *gorm.DB, tenantID uint) (*forms.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(tx).TenantIDEq(tenantID).StatusEq(entity.BillingConfigStatus_Active).All(&billingConfigs)
	if err != nil {
		return nil, err
	}
	if len(billingConfigs) == 0 {
		return nil, billing_errors.ObjectNotExistsError
	} else if len(billingConfigs) != 1 {
		return nil, billing_errors.NewSystemError(errors.New("tenant active billingConfigs error"))
	}
	ret := entity2FormBillingConfig(billingConfigs[0])
	return &ret, nil
}

func (model *BillingConfigModel) GetActiveBillingConfig(tenantID uint) (*forms.BillingConfig, error) {
	var ret *forms.BillingConfig
	var err error
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		ret, err = model.GetActiveBillingConfigWithTx(tx, tenantID)
		return err
	})
	if err != nil {
		return nil, err
	}
	return ret, nil
}

func (model *BillingConfigModel) GetBillingConfigIDWithType(tenantID uint, billingConfigType string) (uint, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).TypeEq(billingConfigType).
		StatusIn(entity.BillingConfigStatus_Active, entity.BillingConfigStatus_Scheduled, entity.BillingConfigStatus_Closing).
		All(&billingConfigs)
	if err != nil {
		return 0, err
	}
	if len(billingConfigs) == 0 {
		return 0, billing_errors.NewObjectNotExistsError(errors.New("GetBillingConfigIDWithType error"))
	}
	if len(billingConfigs) > 1 {
		return 0, billing_errors.NewSystemError(errors.New("GetBillingConfigIDWithType error"))
	}
	return billingConfigs[0].ID, nil
}

func (model *BillingConfigModel) IsMspWithCache(tenantID uint) (bool, error) {
	cache := cachehandler.Get()
	var isMsp bool
	found := cache.Get("IsMspWithCache", &isMsp, tenantID)
	if found {
		return isMsp, nil
	}
	result, err := model.IsMsp(tenantID)
	if err == nil {
		cache.Set("IsMspWithCache", result, tenantID)
	}
	return result, err
}

func (model *BillingConfigModel) IsMsp(tenantID uint) (bool, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).StatusEq(entity.BillingConfigStatus_Active).All(&billingConfigs)
	if err != nil {
		return false, err
	}
	if len(billingConfigs) > 1 || len(billingConfigs) == 0 {
		return false, billing_errors.NewSystemError(errors.New(fmt.Sprintf("tenant active billingConfigs error: length is %d", len(billingConfigs))))
	}
	return billingConfigs[0].Type == entity.BillingConfigType_MSP, nil
}
func (model *BillingConfigModel) IsMspCustomer(tenantID uint) (bool, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).StatusEq(entity.BillingConfigStatus_Active).All(&billingConfigs)
	if err != nil {
		return false, err
	}
	if len(billingConfigs) > 1 || len(billingConfigs) == 0 {
		return false, billing_errors.NewSystemError(errors.New(fmt.Sprintf("tenant active billingConfigs error: length is %d", len(billingConfigs))))
	}
	return billingConfigs[0].Type == entity.BillingConfigType_MSPCustomer, nil
}
func (model *BillingConfigModel) IsMspCustomerByBillingConfigIDWithCache(BillingConfigID uint) (bool, error) {
	cache := cachehandler.Get()
	var IsMspCustomer bool
	found := cache.Get("IsMspCustomerByBillingConfigIDWithCache", &IsMspCustomer, BillingConfigID)
	if found {
		return IsMspCustomer, nil
	}
	result, err := model.IsMspCustomerByBillingConfigID(BillingConfigID)
	if err == nil {
		cache.Set("IsMspCustomerByBillingConfigIDWithCache", result, BillingConfigID)
	}
	return result, err
}

func (model *BillingConfigModel) IsMspCustomerByBillingConfigID(BillingConfigID uint) (bool, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).IDEq(BillingConfigID).All(&billingConfigs)
	if err != nil {
		return false, err
	}
	if len(billingConfigs) > 1 || len(billingConfigs) == 0 {
		return false, billing_errors.NewSystemError(errors.New(fmt.Sprintf("tenant active billingConfigs IsMspCustomerByBillingConfigID error: length is %d", len(billingConfigs))))
	}
	return billingConfigs[0].Type == entity.BillingConfigType_MSPCustomer, nil
}

func (model *BillingConfigModel) GetBillingConfigWithTx(tx *gorm.DB, BillingConfigID uint) (*forms.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(tx).IDEq(BillingConfigID).All(&billingConfigs)
	if err != nil {
		return nil, err
	}
	if len(billingConfigs) > 1 || len(billingConfigs) == 0 {
		return nil, billing_errors.NewSystemError(errors.New(fmt.Sprintf("tenant active billingConfigs IsMspCustomerByBillingConfigID error: length is %d", len(billingConfigs))))
	}
	ret := billingConfigFromEntity(&billingConfigs[0])
	return ret, nil
}

func (model *BillingConfigModel) GetBillingConfig(BillingConfigID uint) (*forms.BillingConfig, error) {
	return model.GetBillingConfigWithTx(db.GetDB(), BillingConfigID)
}

func (model *BillingConfigModel) GetUseBillingConfigByTimeStampWithCache(tenantID uint, timeStamp int64) (*forms.BillingConfig, error) {
	cache := cachehandler.Get()
	var err error
	billingConfigs := []entity.BillingConfig{}
	found := cache.Get("GetUseBillingConfigID", &billingConfigs, tenantID)
	if !found {
		billingConfigs, err = model.GetUseBillingConfigID(tenantID)
		if err == nil {
			cache.Set("GetUseBillingConfigID", &billingConfigs, tenantID)
		}
	}
	for _, bcfg := range billingConfigs {
		if bcfg.StartActiveTime.Unix() <= timeStamp && (bcfg.EndActiveTime == nil || bcfg.EndActiveTime.Unix() > timeStamp) {
			return billingConfigFromEntity(&bcfg), nil
		}
	}
	log.Error("GetUseBillingConfigID tenant BillingConfig error", zap.Uint("tenantID", tenantID), zap.Int64("timeStamp", timeStamp))
	return nil, billing_errors.NewSystemError(errors.New("tenant BillingConfig error"))
}

func (model *BillingConfigModel) GetUseBillingConfigIDByTimeStampWithCache(tenantID uint, timeStamp int64) (uint, error) {
	bcfg, err := model.GetUseBillingConfigByTimeStampWithCache(tenantID, timeStamp)
	if err != nil {
		return 0, err
	}
	return bcfg.BillingConfigID, nil
}

func (model *BillingConfigModel) GetUseBillingConfigIDFromAllByTimeStampWithCache(tenantID uint, timeStamp int64) (uint, error) {
	cache := cachehandler.Get()
	var err error
	billingConfigs := []forms.BillingConfig{}
	found := cache.Get("GetUseBillingConfigIDFromAllByTimeStampWithCache", &billingConfigs, tenantID)
	if !found {
		billingConfigs, err = model.GetTenantIDAllBillingConfigID(tenantID)
		if err == nil {
			cache.Set("GetUseBillingConfigIDFromAllByTimeStampWithCache", &billingConfigs, tenantID)
		}
	}
	for _, bcfg := range billingConfigs {
		if bcfg.StartActiveTime.Unix() <= timeStamp && (bcfg.EndActiveTime == nil || bcfg.EndActiveTime.Unix() > timeStamp) {
			return bcfg.BillingConfigID, nil
		}
	}
	log.Error("GetUseBillingConfigID tenant BillingConfig error", zap.Uint("tenantID", tenantID), zap.Int64("timeStamp", timeStamp))
	return 0, billing_errors.NewSystemError(errors.New("tenant BillingConfig error"))
}
func (model *BillingConfigModel) GetUseBillingConfigsWithCache(tenantID uint) ([]entity.BillingConfig, error) {
	cache := cachehandler.Get()
	var billingConfigs []entity.BillingConfig
	found := cache.Get("GetUseBillingConfigsWithCache", &billingConfigs, tenantID)
	if found {
		return billingConfigs, nil

	}
	billingConfigs, err := model.GetUseBillingConfigID(tenantID)
	if err == nil {
		cache.Set("GetUseBillingConfigsWithCache", &billingConfigs, tenantID)
		return billingConfigs, nil

	}
	log.Error("GetUseBillingConfigID tenant BillingConfig error", zap.Uint("tenantID", tenantID))
	return nil, err
}

func (model *BillingConfigModel) GetUseBillingConfigID(tenantID uint) ([]entity.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).
		StatusIn(entity.BillingConfigStatus_Active, entity.BillingConfigStatus_Closing).
		All(&billingConfigs)
	if err != nil {
		return nil, err
	}

	return billingConfigs, nil
}

func (model *BillingConfigModel) GetManagingBillingConfigEntities(tenantID uint) ([]entity.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).
		StatusIn(entity.BillingConfigStatus_Managing).
		All(&billingConfigs)
	if err != nil {
		return nil, err
	}

	return billingConfigs, nil
}

func (model *BillingConfigModel) GetManagingBillingConfigs(tenantID uint) ([]*forms.BillingConfig, error) {
	billingConfigs, err := model.GetManagingBillingConfigEntities(tenantID)
	if err != nil {
		return nil, err
	}
	ret := make([]*forms.BillingConfig, len(billingConfigs))
	for i := 0; i != len(billingConfigs); i++ {
		ret[i] = billingConfigFromEntity(&billingConfigs[i])
	}
	return ret, err
}

func (model *BillingConfigModel) GetBillingConfigByManagedTenantID(tenantID uint) (*forms.BillingConfig, error) {
	var billingConfig entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).ManagedTenantIDEq(tenantID).TypeIn(entity.BillingConfigType_MarketplaceReseller).One(&billingConfig)

	if err != nil {
		return nil, err
	}
	return billingConfigFromEntity(&billingConfig), nil
}

func (model *BillingConfigModel) GetBillingConfigIDByManagedTenantID(tenantID uint) (uint, error) {
	billingConfig, err := model.GetBillingConfigByManagedTenantID(tenantID)
	if err != nil {
		return 0, err
	}
	return billingConfig.BillingConfigID, nil
}

func entity2FormBillingConfig(entity entity.BillingConfig) forms.BillingConfig {
	return forms.BillingConfig{
		BillingConfigID: entity.ID,
		TenantID:        entity.TenantID,
		StartActiveTime: &entity.StartActiveTime,
		EndActiveTime:   entity.EndActiveTime,
		Type:            entity.Type,
		Status:          entity.Status}
}
func (model *BillingConfigModel) GetTenantIDAllBillingConfigID(tenantID uint) ([]forms.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	var resBillingConfigs []forms.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).All(&billingConfigs)
	if err != nil {
		return nil, err
	}
	for _, billingConfig := range billingConfigs {
		resBillingConfigs = append(resBillingConfigs, entity2FormBillingConfig(billingConfig))
	}

	return resBillingConfigs, nil
}

func (model *BillingConfigModel) CreateBillingConfigWithTx(tx *gorm.DB, billingConfig *forms.BillingConfig) (*forms.BillingConfig, error) {
	cnt, err := entity.NewBillingConfigQuerySet(tx).TenantIDEq(billingConfig.TenantID).
		TypeEq(billingConfig.Type).ManagedTenantIDEq(billingConfig.ManagedTenantID).
		StatusIn(entity.BillingConfigStatus_Active, entity.BillingConfigStatus_Closing, entity.BillingConfigStatus_Scheduled, entity.BillingConfigStatus_Managing).
		Count()
	if err != nil {
		return nil, err
	}
	if cnt > 0 {
		return nil, billing_errors.NewObjectAlreadyExistsError(errors.New("tenant BillingConfig already exists"))
	}
	var startActiveTime = timeutils.GetNow()
	if billingConfig.StartActiveTime != nil {
		startActiveTime = *billingConfig.StartActiveTime
	}
	needUniqueMark := true
	billingConfigEntity := entity.BillingConfig{
		TenantID:        billingConfig.TenantID,
		StartActiveTime: startActiveTime,
		EndActiveTime:   billingConfig.EndActiveTime,
		Type:            billingConfig.Type,
		Status:          billingConfig.Status,
		ManagedTenantID: billingConfig.ManagedTenantID,
		NeedUniqueMark:  &needUniqueMark,

		BindSupportPlanOnEnabled: billingConfig.BindSupportPlanOnEnabled,
		SupportPlanID:            billingConfig.SupportPlanID,
		SupportPlanExpiredDate:   billingConfig.SupportPlanExpiredDate,
	}
	err = billingConfigEntity.Create(tx)
	if err != nil {
		if errors.Is(err, billing_errors.DuplicateRequestError) {
			var ent entity.BillingConfig
			err = entity.NewBillingConfigQuerySet(tx).TenantIDEq(billingConfig.TenantID).TypeEq(billingConfig.Type).ManagedTenantIDEq(billingConfig.ManagedTenantID).One(&ent)
			if err != nil {
				if errors.Is(err, billing_errors.ObjectNotExistsError) {
					log.Info("billing config is in progress on other uncommitted transaction", zap.Uint("tenantID", billingConfig.TenantID), zap.String("type", billingConfig.Type), zap.Uint("managedTenantID", billingConfig.ManagedTenantID))
					return nil, billing_errors.NewObjectAlreadyExistsError(errors.New("tenant BillingConfig already exists"))
				}
				return nil, err
			}
			if ent.Status == billingConfig.Status {
				log.Info("billing config is progressed by other committed transaction", zap.Uint("tenantID", billingConfig.TenantID), zap.String("type", billingConfig.Type), zap.Uint("managedTenantID", billingConfig.ManagedTenantID))
				return nil, billing_errors.NewObjectAlreadyExistsError(errors.New("tenant BillingConfig already exists"))
			}
		}
		return nil, err
	}
	bcfg := entity2FormBillingConfig(billingConfigEntity)
	if bcfg.Status == entity.BillingConfigStatus_Active || bcfg.Status == entity.BillingConfigStatus_Managing {
		err = eventbushandler.Get().PublishEventWithTransaction(constant.BillingConfigSwitchedEvent, &eventbushandler.Event{
			EventID:   fmt.Sprint(billingConfigEntity.ID),
			EventType: constant.BillingConfigSwitchedEvent,
			EventData: &bcfg,
		}, tx)
		if err != nil {
			log.Error("failed to send billing config switched event on creating", zap.Error(err), zap.Uint("tenantID", billingConfig.TenantID), zap.String("type", billingConfig.Type), zap.Uint("managedTenantID", billingConfig.ManagedTenantID))
			return nil, err
		}
	}
	return &bcfg, nil
}

func (model *BillingConfigModel) CreateBillingConfigAndActive(billingConfig forms.BillingConfig) (*forms.BillingConfig, error) {
	billingConfig.Status = entity.BillingConfigStatus_Active
	var ret *forms.BillingConfig
	var err error
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		ret, err = model.CreateBillingConfigWithTx(tx, &billingConfig)
		return err
	})
	return ret, err
}

func (model *BillingConfigModel) CreateBillingConfigAndScheduled(billingConfig forms.BillingConfig) (*forms.BillingConfig, error) {
	billingConfig.Status = entity.BillingConfigStatus_Scheduled
	var ret *forms.BillingConfig
	var err error
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		ret, err = model.CreateBillingConfigWithTx(tx, &billingConfig)
		return err
	})
	return ret, err
}

func (model *BillingConfigModel) CancelScheduledBillingConfig(tenantID uint) error {
	return db.GetDB().Transaction(func(tx *gorm.DB) error {
		cnt, err := entity.NewBillingConfigQuerySet(tx.Clauses(clause.Locking{Strength: "UPDATE"})).TenantIDEq(tenantID).StatusIn(entity.BillingConfigStatus_Scheduled).Count()
		if err != nil {
			return err
		}
		if cnt != 1 {
			log.Error("scheduled tenant billing config count is invalid", zap.Uint("tenantID", tenantID), zap.Int64("cnt", cnt))
			return billing_errors.NewInvalidStatusError(errors.New("tenant Scheduled BillingConfig error"))
		}
		managerBillingConfigCount, err := entity.NewBillingConfigQuerySet(tx.Clauses(clause.Locking{Strength: "UPDATE"})).ManagedTenantIDEq(tenantID).StatusIn(entity.BillingConfigStatus_Scheduled).Count()
		if err != nil {
			return err
		}
		if cnt > 1 {
			log.Error("scheduled manager tenant billing config count is invalid", zap.Uint("managedTenantID", tenantID), zap.Int64("cnt", managerBillingConfigCount))
			return billing_errors.NewInvalidStatusError(errors.New("tenant Scheduled BillingConfig error"))
		}
		now := timeutils.GetNow()
		err = entity.NewBillingConfigQuerySet(tx).TenantIDEq(tenantID).
			StatusEq(entity.BillingConfigStatus_Scheduled).GetUpdater().SetStatus(entity.BillingConfigStatus_Closing).SetClosedTime(&now).Update()
		if err != nil {
			return err
		}
		if managerBillingConfigCount == 1 {
			log.Info("cancel scheduled manager billing config", zap.Uint("managedTenantID", tenantID))
			err = entity.NewBillingConfigQuerySet(tx).ManagedTenantIDEq(tenantID).
				StatusEq(entity.BillingConfigStatus_Scheduled).GetUpdater().SetStatus(entity.BillingConfigStatus_Closing).SetClosedTime(&now).Update()
			if err != nil {
				return err
			}
		}
		return nil
	})
}

func (model *BillingConfigModel) EventCreateBillingConfig(orgEventData *forms.OrgEventData, tenantInfo *forms.Tenant) (*forms.BillingConfig, error) {
	billingConfigType := entity.BillingConfigType_DirectCustomer
	if tenantInfo.IsMsp {
		billingConfigType = entity.BillingConfigType_MSP
	}
	if tenantInfo.IsMspCustomer {
		billingConfigType = entity.BillingConfigType_MSPCustomer
	}
	if tenantInfo.IsCPPOReseller {
	}
	switch tenantInfo.OrgTag {
	case constant.OrgTagMsp:
		billingConfigType = entity.BillingConfigType_MSP
	case constant.OrgTagMspCustomer:
		billingConfigType = entity.BillingConfigType_MSPCustomer
	case constant.OrgTagAwsCppo:
		billingConfigType = entity.BillingConfigType_MarketplaceReseller
	case constant.OrgTagGcpMCPOReseller:
		billingConfigType = entity.BillingConfigType_MarketplaceReseller
	case constant.OrgTagResalePartner:
		billingConfigType = entity.BillingConfigType_ResalePartner
	case constant.OrgTagResaleCustomer:
		billingConfigType = entity.BillingConfigType_ResaleCustomer
	}
	var startActiveTime = time.Unix(0, 0)
	if tenantInfo.ThirdPartyAccountProvider == constant.AliCloudAccountProvider {
		return model.CreateAlicloudCustomerBillingConfig(uint(orgEventData.OrgId), startActiveTime)
	}
	return model.CreateBillingConfigAndActive(forms.BillingConfig{
		TenantID:        uint(orgEventData.OrgId),
		StartActiveTime: &startActiveTime,
		EndActiveTime:   nil,
		Type:            billingConfigType,
	})
}

func (model *BillingConfigModel) GetAllScheduledBillingConfigs() ([]entity.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).StatusIn(entity.BillingConfigStatus_Scheduled, entity.BillingConfigStatus_Closing).
		All(&billingConfigs)
	if err != nil {
		return nil, err
	}

	return billingConfigs, nil
}

func (model *BillingConfigModel) GetTenantIDScheduledBillingConfig(tenantID uint) ([]entity.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).StatusEq(entity.BillingConfigStatus_Scheduled).TenantIDEq(tenantID).
		All(&billingConfigs)
	if err != nil {
		return nil, err
	}

	return billingConfigs, nil
}

func (model *BillingConfigModel) ClosedBillingConfig(tenantID uint, billingConfigID uint) error {
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).IDEq(billingConfigID).
		StatusEq(entity.BillingConfigStatus_Closing).GetUpdater().SetStatus(entity.BillingConfigStatus_Closed).SetNeedUniqueMark(nil).Update()
	if err != nil {
		return err
	}
	return nil
}

func (model *BillingConfigModel) ListAllBillingConfigs(tenantID uint) ([]forms.BillingConfig, error) {
	var entities []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).All(&entities)
	if err != nil {
		return nil, err
	}
	ret := make([]forms.BillingConfig, len(entities))
	for i, ent := range entities {
		ret[i] = entity2FormBillingConfig(ent)
	}
	return ret, nil
}

func (model *BillingConfigModel) SwitchBillingConfig(billingConfig entity.BillingConfig, isUpdateActiveTime bool) error {
	var billingConfigs []entity.BillingConfig
	delayDays := config.Get().GetInt(config.BillingConfigDelayDays)
	tenantID := billingConfig.TenantID
	OldEndActiveTime := billingConfig.StartActiveTime
	OldClosedTime := OldEndActiveTime.AddDate(0, 0, delayDays)

	err := entity.NewBillingConfigQuerySet(db.GetDB()).TenantIDEq(tenantID).StatusEq(entity.BillingConfigStatus_Active).
		All(&billingConfigs)
	if err != nil {
		return err
	}
	if len(billingConfigs) != 1 && billingConfig.Type != entity.BillingConfigType_MarketplaceReseller {
		log.Error("SwitchBillingConfig error", zap.Uint("tenantID", tenantID), zap.Any("billingConfigs", billingConfigs))
		return errors.New("SwitchBillingConfig len billingConfigs error")
	}
	log.Info("start to switching BillingConfig", zap.Uint("tenantID", tenantID), zap.Any("billingConfigs", billingConfigs), zap.Bool("isUpdateActiveTime", isUpdateActiveTime))
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		var err error
		bcfg, err := model.GetBillingConfigWithTx(tx, billingConfig.ID)
		if err != nil {
			return err
		}
		//BillingConfigType_MarketplaceReseller not need modify active billing config.
		if bcfg.Type != entity.BillingConfigType_MarketplaceReseller {
			err = entity.NewBillingConfigQuerySet(tx).TenantIDEq(tenantID).
				StatusEq(entity.BillingConfigStatus_Active).GetUpdater().SetStatus(entity.BillingConfigStatus_Closing).
				SetEndActiveTime(&OldEndActiveTime).SetClosedTime(&OldClosedTime).Update()
		}

		if err != nil {
			return err
		}
		if !isUpdateActiveTime {
			switch bcfg.Type {
			case entity.BillingConfigType_MarketplaceReseller:
				err = entity.NewBillingConfigQuerySet(tx).TenantIDEq(tenantID).IDEq(billingConfig.ID).
					TypeIn(entity.BillingConfigType_MarketplaceReseller).
					StatusEq(entity.BillingConfigStatus_Scheduled).GetUpdater().SetStatus(entity.BillingConfigStatus_Managing).Update()
				bcfg.Status = entity.BillingConfigStatus_Managing
			default:
				err = entity.NewBillingConfigQuerySet(tx).TenantIDEq(tenantID).IDEq(billingConfig.ID).
					StatusEq(entity.BillingConfigStatus_Scheduled).
					TypeNotIn(entity.BillingConfigType_MarketplaceReseller).
					GetUpdater().SetStatus(entity.BillingConfigStatus_Active).Update()
			}
			if err != nil {
				return err
			}
		} else {
			if bcfg.Type == entity.BillingConfigType_MarketplaceResellerManagedCustomer {
				err = entity.NewBillingConfigQuerySet(tx).ManagedTenantIDEq(tenantID).IDEq(billingConfig.ID).
					TypeIn(entity.BillingConfigType_MarketplaceReseller).
					StatusEq(entity.BillingConfigStatus_Scheduled).GetUpdater().
					SetStartActiveTime(billingConfig.StartActiveTime).
					SetStatus(entity.BillingConfigStatus_Managing).Update()
				if err != nil {
					return err
				}
			}
			err = entity.NewBillingConfigQuerySet(tx).TenantIDEq(tenantID).IDEq(billingConfig.ID).
				StatusEq(entity.BillingConfigStatus_Scheduled).GetUpdater().
				SetStatus(entity.BillingConfigStatus_Active).SetStartActiveTime(billingConfig.StartActiveTime).Update()
			if err != nil {
				return err
			}
		}
		err = eventbushandler.Get().PublishEventWithTransaction(constant.BillingConfigSwitchedEvent, &eventbushandler.Event{
			EventID:   fmt.Sprint(tenantID),
			EventType: constant.BillingConfigSwitchedEvent,
			EventData: bcfg,
		}, tx)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}

func (model *BillingConfigModel) CreateMarketplaceResellerBillingConfig(request forms.MarketplaceResellerBillingConfig) (uint, uint, error) {
	//Customer Billing config
	tenantID, resellerID := request.TenantID, request.ResellerId
	customerBillingConfigReq := forms.BillingConfig{
		TenantID:        tenantID,
		StartActiveTime: request.EffectiveDate,
		EndActiveTime:   nil,
		Type:            entity.BillingConfigType_MarketplaceResellerManagedCustomer,
	}
	if request.SupportPlanInfo != nil && request.SupportPlanInfo.SupportPlanID != 0 {
		log.Info("need to bind support plan on billing config enabled", zap.Uint("supportPlanID", request.SupportPlanInfo.SupportPlanID), zap.Uint("customerID", tenantID))
		customerBillingConfigReq.BindSupportPlanOnEnabled = true
		customerBillingConfigReq.SupportPlanID = &request.SupportPlanInfo.SupportPlanID
		customerBillingConfigReq.SupportPlanExpiredDate = request.SupportPlanInfo.ExpiredDate
	}
	customerBillingConfig, err := model.CreateBillingConfigAndScheduled(customerBillingConfigReq)
	if err != nil {
		log.Error("Create Customer BillingConfig error", zap.Uint("tenantID", request.TenantID), zap.Error(err))
		return 0, 0, errors.New("Create Customer BillingConfig create  error")
	}
	log.Info("success to create customer billing config", zap.Uint("customerID", tenantID))
	//Payment billing config
	paymentBillingConfigReq := forms.BillingConfig{
		TenantID:        resellerID,
		StartActiveTime: request.EffectiveDate,
		EndActiveTime:   nil,
		Type:            entity.BillingConfigType_MarketplaceReseller,
		ManagedTenantID: tenantID,
	}
	paymentBillingConfig, err := model.CreateBillingConfigAndScheduled(paymentBillingConfigReq)
	if err != nil {
		log.Error("Create Reseller BillingConfig Payment billing config error", zap.Uint("tenantID", request.TenantID), zap.Uint("resellerID", request.ResellerId), zap.Error(err))
		return 0, 0, errors.New("CreateCPPOBillingConfig create  error")
	}
	log.Info("success to create reseller billing config", zap.Uint("reseller", resellerID))
	return customerBillingConfig.BillingConfigID, paymentBillingConfig.BillingConfigID, nil
}

func (model *BillingConfigModel) CreateAlicloudCustomerBillingConfig(tenantID uint, effectiveDate time.Time) (*forms.BillingConfig, error) {
	tenantInfo, err := centralhandler.GetCentralHandler().GetTenant(tenantID)
	if err != nil {
		return nil, err
	}
	subscription, err := (&aliyunsubscriptionmngt.AliyunSubscriptionMngt{}).GetLastSubscriptionRecodByAliUid(tenantInfo.ThirdPartyAccountCode)
	if err != nil {
		log.Error("failed to get subscription record for alicloud", zap.Uint("tenantID", tenantID), zap.Error(err))
		return nil, err
	}
	billingConfigType := entity.BillingConfigType_AlicloudCustomer
	if subscription.IsThirdParty {
		billingConfigType = entity.BillingConfigType_DirectCustomer
	}
	newConfigReq := forms.BillingConfig{
		TenantID:        tenantID,
		StartActiveTime: &effectiveDate,
		EndActiveTime:   nil,
		Type:            billingConfigType,
	}
	if !timeutils.GetNow().Before(effectiveDate) {
		return model.CreateBillingConfigAndActive(newConfigReq)
	} else {
		return model.CreateBillingConfigAndScheduled(newConfigReq)
	}
}

func (model *BillingConfigModel) ListAlicloudCustomerBillingConfigs() ([]*forms.BillingConfig, error) {
	var billingConfigs []entity.BillingConfig
	err := entity.NewBillingConfigQuerySet(db.GetDB()).TypeEq(entity.BillingConfigType_AlicloudCustomer).
		StatusIn(entity.BillingConfigStatus_Active, entity.BillingConfigStatus_Closing, entity.BillingConfigStatus_Closed).
		All(&billingConfigs)
	if err != nil {
		return nil, err
	}
	var result []*forms.BillingConfig
	for _, billingConfig := range billingConfigs {
		if slices.Contains(config.Get().GetStringSlice(config.AlicloudInvisibleCustomers), fmt.Sprintf("%d-%d", billingConfig.TenantID, billingConfig.ID)) {
			log.Info("skip invisible org for alicloud", zap.Uint("tenantID", billingConfig.TenantID), zap.Uint("billingConfig.ID", billingConfig.ID))
			continue
		}
		result = append(result, billingConfigFromEntity(&billingConfig))
	}
	return result, err
}
