package plan

import (
	"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/centralhandler"
	"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/marketing"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
)

func (m *TenantPlanModel) GetTenantPOCPlanInfo(tenantID uint) (*forms.PlanInfo, error) {
	return m.getTenantPlanInfo(tenantID, 0, entity.TenantPlan_POC)
}

func (m *TenantPlanModel) EnableTenantPOCPlanAfterCreditsOpened(tenantID uint) error {
	pocPlanInfo, err := m.GetTenantPOCPlanInfo(tenantID)
	if err != nil {
		return err
	}

	if pocPlanInfo.Status == entity.TenantPlanStatus_Active {
		log.Info("poc plan already active", zap.Uint("tenantID", tenantID))
		return nil
	}

	creditsSummary, err := (&marketing.CreditsPackageModel{}).GetTenantCreditsSummary(tenantID, 0)
	if err != nil {
		return err
	}

	if creditsSummary.ActivePOCCreditsPackagesCount == 0 {
		log.Info("no poc credits to active poc", zap.Uint("tenantID", tenantID))
		return nil
	}
	billingConfigID, err := m.getTenantBillingConfigID(tenantID)
	if err != nil {
		log.Error("EnableTenantPOCPlanAfterCreditsOpened tenant get billing config  error", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	if pocPlanInfo.PlanID == 0 {
		tenantPlanEntity := entity.TenantPlan{
			TenantID:              tenantID,
			Plan:                  entity.TenantPlan_POC,
			Status:                entity.TenantPlanStatus_NotStarted,
			BindedPackageID:       0,
			BindedBillingConfigID: billingConfigID,
		}
		err = tenantPlanEntity.Create(db.GetDB())
		if err != nil {
			log.Error("failed to create tenant plan entity when enable poc plan", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
	}
	err = m.unbindPoCRemainDaysPackage(pocPlanInfo)
	if err != nil {
		return err
	}
	err = m.activeTenantPlan(tenantID, billingConfigID, entity.TenantPlan_POC)
	if err != nil {
		log.Error("failed to active tenant poc plan", zap.Uint("tenantID", tenantID), zap.Error(err))
	}
	log.Info("successful open tenant poc plan", zap.Uint("tenantID", tenantID))
	return nil
}

func (m *TenantPlanModel) unbindPoCRemainDaysPackage(pocPlanInfo *forms.PlanInfo) (err error) {
	if pocPlanInfo.BindedPackageID == 0 {
		return nil
	}
	err = (&billing.BindedPackageModel{}).UnbindPackage(pocPlanInfo.BindedPackageID)
	if err != nil {
		log.Error("failed to unbind poc plan remain days package", zap.Uint("PlanID", pocPlanInfo.PlanID), zap.Uint("bindedPackageID", pocPlanInfo.BindedPackageID), zap.Error(err))
		return err
	}
	err = entity.NewTenantPlanQuerySet(db.GetDB()).IDEq(pocPlanInfo.PlanID).PlanEq(entity.TenantPlan_POC).GetUpdater().SetBindedPackageID(0).Update()
	if err != nil {
		log.Error("failed to reset poc plan remain days package", zap.Uint("PlanID", pocPlanInfo.PlanID), zap.Uint("bindedPackageID", pocPlanInfo.BindedPackageID), zap.Error(err))
		return err
	}
	log.Info("success unbind poc plan remain days package", zap.Uint("PlanID", pocPlanInfo.PlanID), zap.Uint("bindedPackageID", pocPlanInfo.BindedPackageID))
	return nil
}

func (m *TenantPlanModel) DisableTenantPOCPlanAfterCreditsExhausted(creditsPackageID uint) error {
	creditsPackage, err := (&marketing.CreditsPackageModel{}).GetCreditsPackage(creditsPackageID)
	if err != nil {
		return err
	}
	tenantID := creditsPackage.TenantID
	pocPlanInfo, err := m.GetTenantPOCPlanInfo(tenantID)
	if err != nil {
		return err
	}

	creditsSummary, err := (&marketing.CreditsPackageModel{}).GetTenantCreditsSummary(tenantID, 0)
	if err != nil {
		return err
	}

	if creditsSummary.ActivePOCCreditsPackagesCount != 0 {
		log.Info("still have poc credits packages alive", zap.Uint("tenantID", tenantID))
		return nil
	}

	if pocPlanInfo.Status == entity.TenantPlanStatus_Closed || pocPlanInfo.Status == entity.TenantPlanStatus_Closing {
		log.Info("poc plan already closed", zap.Uint("tenantID", tenantID))
		return nil
	}

	if pocPlanInfo.Status == entity.TenantPlanStatus_NotStarted {
		log.Info("poc plan not active", zap.Uint("tenantID", tenantID))
		return nil
	}
	billingConfigID, err := (&billingconfig.BillingConfigModel{}).GetActiveBillingConfigID(tenantID)

	// 20250708 for poc Exhausted
	tenant, err := centralhandler.GetCentralHandler().GetTenant(tenantID)
	if err != nil {
		log.Error("failed to get tenant", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	if tenant.Kind == forms.TenantKindInternal || pocPlanInfo.StartedAt.Before(config.Get().GetTime(config.PocAfterNoPayable)) {
		onDemandPlanInfo, err := m.GetTenantOnDemandPlanInfo(tenantID, billingConfigID)
		if err != nil {
			log.Error("failed to GetTenantOnDemandPlanInfo", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
		if onDemandPlanInfo.Status == entity.TenantPlanStatus_Active {
			// just skip send poc end when ondemand plan is active
			err = m.closeTenantPlan(tenantID, billingConfigID, entity.TenantPlan_POC)
			if err != nil {
				log.Error("failed to close tenant poc plan", zap.Uint("tenantID", tenantID), zap.Error(err))
				return err
			}
			log.Info("successful closed tenant poc plan", zap.Uint("tenantID", tenantID))
			return nil
		}
		credits := decimal.NewFromInt(config.Get().GetInt64(config.POCRemainDaysCredits))

		bindedPackageInfo, err := (&billing.BindedPackageModel{}).BindPackageExtra(tenantID, &forms.BindedPackageInfoInput{
			PackageID:             config.Get().GetUint(config.POCPackage),
			ChargeCredits:         &credits,
			BindedBillingConfigID: &billingConfigID,
		})
		if err != nil {
			log.Error("failed to bind poc remain days packages", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}

		log.Info("success binded poc remain days packages", zap.Uint("tenantID", tenantID), zap.Uint("bindedPackageID", bindedPackageInfo.BindedPackageID))
		err = entity.NewTenantPlanQuerySet(db.GetDB()).TenantIDEq(tenantID).PlanEq(entity.TenantPlan_POC).GetUpdater().SetBindedPackageID(bindedPackageInfo.BindedPackageID).Update()
		if err != nil {
			log.Error("failed to update tenant poc plan entity", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
		err = m.sendEmailAfterPocCreditsClosed(tenant, creditsPackage)
		if err != nil {
			return err
		}
		err = m.closingTenantPlan(tenantID, billingConfigID, entity.TenantPlan_POC)
		if err != nil {
			log.Error("failed to closing tenant poc plan", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
		log.Info("successful to closing  tenant poc plan", zap.Uint("tenantID", tenantID))

	} else {
		err = m.sendEmailAfterPocCreditsClosed(tenant, creditsPackage)
		if err != nil {
			return err
		}
		err = m.closePOCPlan(tenantID, pocPlanInfo)
		if err != nil {
			log.Error("failed to closed tenant poc plan", zap.Uint("tenantID", tenantID), zap.Error(err))
			return err
		}
		log.Info("successful to closed  tenant poc plan", zap.Uint("tenantID", tenantID))

	}

	return nil
}

func (m *TenantPlanModel) closePOCPlan(tenantID uint, planInfo *forms.PlanInfo) (err error) {
	err = m.unbindPoCRemainDaysPackage(planInfo)
	if err != nil {
		return err
	}
	err = m.closeTenantPlan(tenantID, *planInfo.BindedBillingConfigID, entity.TenantPlan_POC)
	if err != nil {
		log.Error("failed to update tenant poc plan", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	log.Info("successful closed tenant poc plan", zap.Uint("tenantID", tenantID))
	return nil
}

func (m *TenantPlanModel) POCCleanup(tenantID uint) error {
	pocPlanInfo, err := m.GetTenantPOCPlanInfo(tenantID)
	if err != nil {
		return err
	}

	if pocPlanInfo.Status != entity.TenantPlanStatus_Closing {
		log.Info("poc plan not in closing", zap.Uint("tenantID", tenantID))
		return nil
	}

	ondemandPlanInfo, err := m.GetTenantOnDemandPlanInfo(tenantID, 0)
	if err != nil {
		return err
	}

	if ondemandPlanInfo.Status == entity.TenantPlanStatus_Active {
		// just skip cleanup poc end email when ondemand plan is active
		err = m.closePOCPlan(tenantID, pocPlanInfo)
		if err != nil {
			return err
		}
		return nil
	}

	if !config.Get().GetBool(config.EnablePOCEndedCleanup) {
		log.Info("poc clean up disabled.", zap.Uint("tenantID", tenantID))
		return nil
	}

	err = m.closePOCPlan(tenantID, pocPlanInfo)
	if err != nil {
		return err
	}
	return nil
}

func (m *TenantPlanModel) GetAllPOCActiveTenantIDs() ([]uint, error) {
	dbResult := []entity.TenantPlan{}
	err := entity.NewTenantPlanQuerySet(db.GetDB()).PlanEq(entity.TenantPlan_POC).StatusEq(entity.TenantPlanStatus_Active).All(&dbResult)
	if err != nil {
		return nil, err
	}
	result := []uint{}
	for _, info := range dbResult {
		result = append(result, info.TenantID)
	}
	return result, nil
}

func (m *TenantPlanModel) GetAllPOCClosingTenantIDs() ([]uint, error) {
	dbResult := []entity.TenantPlan{}
	err := entity.NewTenantPlanQuerySet(db.GetDB()).PlanEq(entity.TenantPlan_POC).StatusEq(entity.TenantPlanStatus_Closing).All(&dbResult)
	if err != nil {
		return nil, err
	}
	result := []uint{}
	for _, info := range dbResult {
		result = append(result, info.TenantID)
	}
	return result, nil
}

func (m *TenantPlanModel) RegisterPOCEventHandler() error {
	eventHandlerFunc := func(event *eventbushandler.Event) error {
		return m.EnableTenantPOCPlanAfterCreditsOpened(event.EventData.(*forms.TenantID).TenantID)
	}
	err := eventbushandler.Get().RegisterEventHandleFunc(marketing.CreditsPackageOpenEventType, "poc_plan", forms.TenantID{}, eventHandlerFunc)
	if err != nil {
		return err
	}

	eventHandlerFunc = func(event *eventbushandler.Event) error {
		return m.ResetPOCRemindersAfterCreditsCharged(event.EventData.(*forms.TenantID).TenantID)
	}
	err = eventbushandler.Get().RegisterEventHandleFunc(marketing.CreditsPackageOpenEventType, "poc_plan_refresh_reminders", forms.TenantID{}, eventHandlerFunc)
	if err != nil {
		return err
	}

	eventHandlerFunc = func(event *eventbushandler.Event) error {
		return m.DisableTenantPOCPlanAfterCreditsExhausted(event.EventData.(*forms.CreditsPackageID).CreditsPackageID)
	}
	err = eventbushandler.Get().RegisterEventHandleFunc(marketing.CreditsPackageCloseEventType, "poc_plan", forms.CreditsPackageID{}, eventHandlerFunc)
	if err != nil {
		return err
	}

	return nil
}
