package spend_limit

import (
	"context"

	"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/tidbmgmtsvchandler"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// Essential v1 serverless clusters (the legacy shared-endpoint serverless SKU)
// never create spend_limit_settings rows in billing, unlike starter clusters.
// The risk-close throttle/recover actions therefore simply enumerate the
// tenant's live essential v1 clusters from tidb-mgmt-service and publish one
// closing/recovered event per cluster over the same spend-limit event channel
// the starter flow uses. The event payload carries only the cluster identity
// (tenant + range_type=cluster + range_id=cluster id + biz_type) and the
// status; no spend-limit fields are fabricated since essential v1 clusters
// have no spend limit.

// CloseEssentialV1Clusters publishes a "closing" event for every live
// essential v1 serverless cluster of the tenant.
func (m *SpendLimitModel) CloseEssentialV1Clusters(tenantID uint) error {
	log.Info("start to close essential v1 clusters", zap.Uint("tenantID", tenantID))
	return m.publishEssentialV1ClusterStatusEvents(tenantID, entity.SpendLimitStatus_Closing)
}

// RecoverEssentialV1Clusters publishes a "recovered" event for every live
// essential v1 serverless cluster of the tenant.
func (m *SpendLimitModel) RecoverEssentialV1Clusters(tenantID uint) error {
	log.Info("start to recover essential v1 clusters", zap.Uint("tenantID", tenantID))
	return m.publishEssentialV1ClusterStatusEvents(tenantID, entity.SpendLimitStatus_Recovered)
}

func (m *SpendLimitModel) publishEssentialV1ClusterStatusEvents(tenantID uint, status string) error {
	clusters, err := m.listEssentialV1Clusters(tenantID)
	if err != nil {
		log.Error("failed to list essential v1 clusters", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}
	if len(clusters) == 0 {
		log.Info("no essential v1 clusters", zap.Uint("tenantID", tenantID))
		return nil
	}
	err = db.GetDB().Transaction(func(tx *gorm.DB) error {
		for _, cluster := range clusters {
			if cluster == nil {
				continue
			}
			event := &forms.SpendLimitInfo{
				SpendLimitSetting: forms.SpendLimitSetting{
					TenantID:   tenantID,
					RangeType:  entity.SpendLimitRangeType_Cluster,
					RangeID:    cluster.ID,
					RangeIDStr: cluster.ID,
					BizType:    entity.SpendLimitBizType_ServerlessCluster,
					Status:     status,
				},
			}
			err = m.sendSpendLimitStatusChangeEvent(tx, event)
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		log.Error("failed to publish essential v1 cluster status events", zap.Uint("tenantID", tenantID), zap.String("status", status), zap.Error(err))
		return err
	}
	log.Info("success to publish essential v1 cluster status events", zap.Uint("tenantID", tenantID), zap.String("status", status), zap.Int("count", len(clusters)))
	return nil
}

func (m *SpendLimitModel) listEssentialV1Clusters(tenantID uint) ([]*forms.Cluster, error) {
	mgmtClient, err := tidbmgmtsvchandler.Get()
	if err != nil {
		log.Error("failed to get tidb mgmt service client", zap.Uint("tenantID", tenantID), zap.Error(err))
		return nil, err
	}
	return mgmtClient.GetEssentialV1ClustersByTenantID(context.Background(), uint64(tenantID))
}
