package workers

import (
	"fmt"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/handler/infrahandler"
	"github.com/pingcap/dbaas/billing2/model/billingconfig"
	"github.com/pingcap/dbaas/billing2/model/costexplorer"
	"github.com/pingcap/dbaas/billing2/utils/concurrency"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"github.com/pingcap/dbaas/billing2/utils/timeutils"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"slices"
	"time"
)

func NoTrafficDedicatedOrgCheckWorker() error {
	billingMonth := timeutils.GetLastMonth(timeutils.GetNow().Add(-time.Hour * config.Get().GetDuration(config.Invoicehours)))
	return concurrency.RunInPoolWithAllTenants(
		config.Get().GetUint(config.NoTrafficCheckerRunConcurrency),
		func(tenantID uint) {
			err := NoTrafficDedicatedOrgChecker(tenantID, billingMonth)
			if err != nil {
				log.Error("Failed to run NoTrafficDedicatedOrgChecker", zap.Error(err))
			}
		},
	)
}

func NoTrafficDedicatedOrgChecker(tenantID uint, billingMonth time.Time) error {
	clusters, err := infrahandler.Get().ListDedicatedClusters(tenantID)
	if err != nil {
		return err
	}
	billingConfigs, err := (&billingconfig.BillingConfigModel{}).ListAllBillingConfigs(tenantID)
	if err != nil {
		return err
	}
	billingConfigIDs := make([]uint, len(billingConfigs))
	for i, cfg := range billingConfigs {
		billingConfigIDs[i] = cfg.BillingConfigID
	}
	start := timeutils.GetMonthDate(billingMonth)
	end := timeutils.GetMonthDate(billingMonth.AddDate(0, 1, 0))
	internal := false

	for _, cluster := range clusters {
		log.Info("start to check dedicated traffic", zap.Time("start", start), zap.Uint("clusterID", cluster.ID), zap.Uint("tenantID", tenantID))
		res, err := costexplorer.CostTrendsWithMultiBillingConfigs(tenantID, billingConfigIDs, &forms.CostExploreTrendsInput{
			CostExplorerTrendsFilter: forms.CostExplorerTrendsFilter{
				StartDate: &start,
				EndDate:   &end,
				Internal:  &internal,
				Clusters:  []string{fmt.Sprintf("%d", cluster.ID)},
					SplitLoopDeadline: time.Now().Add(config.Get().GetDuration(config.WorkerSplitLoopTimeoutSeconds) * time.Second),
			},
			BreakdownBy: []forms.CostExplorerTrendsBreakdownKey{forms.CostExploreTrendsBreakdownKeyService},
		})
		if err != nil {
			log.Error("failed to query cluster cost", zap.Time("start", start), zap.Uint("clusterID", cluster.ID), zap.Uint("tenantID", tenantID), zap.Error(err))
		}
		enableThreshold := config.Get().GetString(config.NoTrafficCheckerEnableCheckThreshold)
		if res.RawPrice.LessThanOrEqual(decimal.RequireFromString(enableThreshold)) {
			log.Info("dedicated cost is zero", zap.String("enableThreshold", enableThreshold), zap.Time("start", start), zap.Uint("clusterID", cluster.ID), zap.Uint("tenantID", tenantID))
			continue
		}
		if len(res.Elements) != 1 {
			log.Error("invalid output", zap.Any("result", res))
			continue
		}
		dbCostWithoutTraffic := decimal.Zero
		trafficCost := decimal.Zero
		dbCostWithoutTrafficExcludeCatNames := config.Get().GetStringSlice(config.NoTrafficCheckerDBCostWithoutTrafficExcludeCatNames)
		trafficCostCatNames := config.Get().GetStringSlice(config.NoTrafficCheckerTrafficCostCatNames)
		for _, elem := range res.Elements[0].Breakdown {
			if elem.Category == nil {
				log.Error("invalid output", zap.Any("result", res), zap.Any("element", elem))
				return nil
			}
			cat := *elem.Category
			if slices.Contains(trafficCostCatNames, cat) {
				trafficCost = trafficCost.Add(elem.RawPrice)
			} else if !slices.Contains(dbCostWithoutTrafficExcludeCatNames, cat) {
				dbCostWithoutTraffic = dbCostWithoutTraffic.Add(elem.RawPrice)
			}
		}
		if !dbCostWithoutTraffic.LessThanOrEqual(decimal.RequireFromString(enableThreshold)) && trafficCost.IsZero() {
			log.Error("active cluster but no traffic cost", zap.String("dbCostWithoutTraffic", dbCostWithoutTraffic.String()), zap.String("trafficCost", trafficCost.String()), zap.Uint("clusterID", cluster.ID), zap.Uint("tenantID", tenantID))
		}
		log.Info("cluster traffic cost check pass", zap.String("dbCostWithoutTraffic", dbCostWithoutTraffic.String()), zap.String("trafficCost", trafficCost.String()), zap.Uint("clusterID", cluster.ID), zap.Uint("tenantID", tenantID))
	}
	return nil
}
