package plan

import (
	"errors"

	"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/eventbushandler"
	"github.com/pingcap/dbaas/billing2/model/billing"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/payment/paymentsub"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func (m *TenantPlanModel) GetTenantOnDemandPlanInfo(tenantID uint, billingConfigID uint) (*forms.PlanInfo, error) {
	return m.getTenantPlanInfo(tenantID, billingConfigID, entity.TenantPlan_OnDemand)
}

func (m *TenantPlanModel) EnableTenantOnDemandPlan(tenantID uint) error {
	billingConfigID, err := m.getTenantBillingConfigID(tenantID)
	if err != nil {
		log.Error("EnableTenantOnDemandPlan tenant get billing config error", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	return m.EnableTenantOnDemandPlanByBillingConfigID(tenantID, billingConfigID)
}

func (m *TenantPlanModel) EnableTenantOnDemandPlanByBillingConfigID(tenantID, billingConfigID uint) error {
	payable, err := (&paymentsub.PaymentModel{}).BillingConfigPayable(tenantID, billingConfigID)
	if err != nil {
		return err
	}

	if !payable {
		log.Info("not payable, stopping enable on demand plan", zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfigID))
		return nil
	}

	planInfo, err := m.GetTenantOnDemandPlanInfo(tenantID, billingConfigID)
	if err != nil {
		return err
	}
	if planInfo.Status == entity.TenantPlanStatus_Active {
		return nil
	}

	if planInfo.PlanID == 0 {
		tenantPlanEntity := entity.TenantPlan{
			TenantID:              tenantID,
			Plan:                  entity.TenantPlan_OnDemand,
			Status:                entity.TenantPlanStatus_NotStarted,
			BindedBillingConfigID: billingConfigID,
		}
		err = tenantPlanEntity.Create(db.GetDB().Clauses(clause.Insert{Modifier: "IGNORE"}))
		if err != nil {
			log.Error("failed to create ondemand tenant plan", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
	}
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		var tenantPlanEntity entity.TenantPlan
		err := entity.NewTenantPlanQuerySet(tx.Clauses(clause.Locking{Strength: "UPDATE"})).
			TenantIDEq(tenantID).BindedBillingConfigIDEq(billingConfigID).PlanEq(entity.TenantPlan_OnDemand).One(&tenantPlanEntity)
		if err != nil {
			return err
		}
		if tenantPlanEntity.BindedPackageID == 0 {
			bindedPackageID, err := m.bindOnDemandPackage(tx, tenantID, billingConfigID)
			if err != nil {
				return err
			}
			err = entity.NewTenantPlanQuerySet(tx).IDEq(tenantPlanEntity.ID).GetUpdater().SetBindedPackageID(bindedPackageID).Update()
			if err != nil {
				return err
			}
		} else {
			err = (&billing.BindedPackageModel{}).UpdatePackageInternalMark(tx, tenantID, tenantPlanEntity.BindedPackageID, false)
			if err != nil {
				return err
			}
		}
		return m.setTenantPlanStatusWithTx(tx, tenantID, billingConfigID, entity.TenantPlan_OnDemand, entity.TenantPlanStatus_Active)
	})
	if err != nil {
		log.Info("failed to active ondemand tenant plan", zap.Error(err), zap.Uint("tenantID", tenantID))
		return err
	}
	log.Info("successfully active ondemand tenant plan", zap.Uint("tenantID", tenantID))
	cachehandler.Get().Delete(constant.GetTenantEffectivePlanCacheName, tenantID)
	return nil
}

func (m *TenantPlanModel) DisableTenantOnDemandPlan(tenantID uint) error {
	billingConfigID, err := m.getTenantBillingConfigID(tenantID)
	if err != nil {
		log.Error("DisableTenantOnDemandPlan tenant get billing config  error", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	return m.DisableTenantOnDemandPlanByBillingConfigID(tenantID, billingConfigID)
}

func (m *TenantPlanModel) DisableTenantOnDemandPlanByBillingConfigID(tenantID, billingConfigID uint) error {
	payable, err := (&paymentsub.PaymentModel{}).BillingConfigPayable(tenantID, billingConfigID)
	if err != nil {
		return err
	}
	if payable {
		return nil
	}
	planInfo, err := m.GetTenantOnDemandPlanInfo(tenantID, billingConfigID)
	if err != nil {
		return err
	}
	if planInfo.Status != entity.TenantPlanStatus_Active {
		return nil
	}
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		var tenantPlanEntity entity.TenantPlan
		err := entity.NewTenantPlanQuerySet(tx.Clauses(clause.Locking{Strength: "UPDATE"})).
			TenantIDEq(tenantID).PlanEq(entity.TenantPlan_OnDemand).One(&tenantPlanEntity)
		if err != nil {
			return err
		}
		//if tenantPlanEntity.BindedPackageID != 0 {
		//	err = (&billing.BindedPackageModel{}).UpdatePackageInternalMark(tx, tenantID, tenantPlanEntity.BindedPackageID, true)
		//	if err != nil {
		//		return err
		//	}
		//}
		return m.setTenantPlanStatusWithTx(tx, tenantID, billingConfigID, entity.TenantPlan_OnDemand, entity.TenantPlanStatus_Closing)
	})
	if err != nil {
		log.Info("failed to inactive ondemand tenant plan", zap.Error(err), zap.Uint("tenantID", tenantID))
		return err
	}
	log.Info("successfully inactive ondemand tenant plan", zap.Uint("tenantID", tenantID))
	cachehandler.Get().Delete(constant.GetTenantEffectivePlanCacheName, tenantID)
	return err
}

func GetOnDemandBindedPackage(tenantID uint, billingConfigID uint) (*forms.BindedPackageInfo, error) {
	onDemandPackageID := config.Get().GetUint(config.OnDemandPackage)
	bindedPackages, err := (&billing.BindedPackageModel{}).GetTenantPackageBindedPackage(tenantID, onDemandPackageID)
	if err != nil {
		log.Error("failed to get bind ondemand package", zap.Uint("tenantID", tenantID), zap.Error(err))
		return nil, err
	} else if len(bindedPackages) != 0 {
		for _, bindedPackage := range bindedPackages {
			if *bindedPackage.BindedBillingConfigID == billingConfigID {
				return &bindedPackage, nil
			}
		}
	}
	return nil, nil
}

func getOnDemandBindedPackageID(tenantID uint, billingConfigID uint) (uint, error) {
	pkg, err := GetOnDemandBindedPackage(tenantID, billingConfigID)
	if err != nil || pkg == nil {
		return 0, err
	}
	return pkg.BindedPackageID, nil
}

func (m *TenantPlanModel) bindOnDemandPackage(tx *gorm.DB, tenantID uint, billingConfigID uint) (uint, error) {
	OnDemandBindedPackageID, err := getOnDemandBindedPackageID(tenantID, billingConfigID)
	if err != nil {
		return 0, err
	} else if OnDemandBindedPackageID != 0 {
		return OnDemandBindedPackageID, nil
	}
	if err != nil {
		return 0, err
	}
	onDemandPackageID := config.Get().GetUint(config.OnDemandPackage)
	bindedPackageInfo, err := (&billing.BindedPackageModel{}).BindPackageExtraWithTx(tx, tenantID, &forms.BindedPackageInfoInput{
		PackageID:             onDemandPackageID,
		DiscountEnabled:       true,
		InvoicePayable:        true,
		BindedBillingConfigID: &billingConfigID,
	})
	if err != nil {
		if errors.Is(err, billing_errors.OutOfMaxBindedTimesError) {
			OnDemandBindedPackageID, _ = getOnDemandBindedPackageID(tenantID, billingConfigID)
			if OnDemandBindedPackageID != 0 {
				return OnDemandBindedPackageID, nil
			}
		}
		log.Error("failed bind ondemand package", zap.Uint("tenantID", tenantID), zap.Error(err))
		return 0, err
	}
	log.Info("successfully bind ondemand package", zap.Uint("tenantID", tenantID))
	return bindedPackageInfo.BindedPackageID, nil
}

func (m *TenantPlanModel) ProcessPayableChange(tenantID uint) error {
	billingConfigs, err := (&billingconfig.BillingConfigModel{}).ListAllBillingConfigs(tenantID)
	if err != nil {
		return err
	}
	var lastErr error
	for _, billingConfig := range billingConfigs {
		if billingConfig.Status == entity.BillingConfigStatus_Closed {
			log.Info("billing config is closed, skip processing", zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfig.BillingConfigID))
			continue
		}
		payable, err := (&paymentsub.PaymentModel{}).BillingConfigPayable(tenantID, billingConfig.BillingConfigID)
		if err != nil {
			return err
		}
		if payable {
			log.Info("enable tenant on demand plan", zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfig.BillingConfigID))
			err = m.EnableTenantOnDemandPlanByBillingConfigID(tenantID, billingConfig.BillingConfigID)
		} else {
			log.Info("disable tenant on demand plan", zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfig.BillingConfigID))
			err = m.DisableTenantOnDemandPlanByBillingConfigID(tenantID, billingConfig.BillingConfigID)
		}
		if err != nil {
			log.Error("failed to process tenant plan on payable changed", zap.Error(err), zap.Uint("tenantID", tenantID), zap.Uint("billingConfigID", billingConfig.BillingConfigID))
			lastErr = err
		}
	}
	return lastErr
}

func (m *TenantPlanModel) RegisterOnDemandEventHandler() (err error) {
	eventHandlerFunc := func(event *eventbushandler.Event) error {
		return m.ProcessPayableChange(event.EventData.(*forms.TenantID).TenantID)
	}
	err = eventbushandler.Get().RegisterEventHandleFunc(paymentsub.TenantPayableEventType, "ondemand_plan", forms.TenantID{}, eventHandlerFunc)
	if err != nil {
		return err
	}
	return nil
}
