package metering

import (
	"context"
	"strconv"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	clusterrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	billingrepo "github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/metering"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	MetricsNamespace        = "global_dedicated_service"
	metricsLabelMeterType   = "meter_type" // available values: project, cluster
	metricsLabelSuccess     = "success"    // available values: true, false
	metricsMeterTypeCluster = "cluster"
	MetricsLblTenantId      = "tenant_id"
	MetricsLblProjectId     = "project_id"
	MetricsLblClusterId     = "cluster_id"
)

var (
	MetricsMeterDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: MetricsNamespace,
			Subsystem: "metering_meter",
			Name:      "duration_seconds",
			Help:      "duration of metering.",
			Buckets:   []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10, 30, 60, 120},
		}, []string{MetricsLblTenantId, MetricsLblProjectId, MetricsLblClusterId, metricsLabelMeterType, metricsLabelSuccess},
	)
	MetricsMeterCount = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: MetricsNamespace,
			Subsystem: "metering_meter",
			Name:      "total",
			Help:      "total meter of target.",
		}, []string{MetricsLblTenantId, MetricsLblProjectId, MetricsLblClusterId, metricsLabelMeterType, metricsLabelSuccess},
	)
	MetricsMeterLag = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: MetricsNamespace,
			Subsystem: "metering_meter",
			Name:      "lag_seconds",
			Help:      "lag of metering for target.",
		}, []string{MetricsLblTenantId, MetricsLblProjectId, MetricsLblClusterId, metricsLabelMeterType},
	)
	MetricsReportLag = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: MetricsNamespace,
			Subsystem: "metering_report",
			Name:      "lag_seconds",
			Help:      "lag of metering data reporting.",
		}, []string{},
	)
)

func init() {
	prometheus.MustRegister(MetricsMeterDuration)
	prometheus.MustRegister(MetricsMeterCount)
	prometheus.MustRegister(MetricsMeterLag)
	prometheus.MustRegister(MetricsReportLag)
}

type LagReporter struct {
	clusterRepo         clusterrepo.Repo
	clusterMeteringRepo billingrepo.ClusterMeteringRepo
	billingReportRecord billingrepo.ReportRecordRepo
}

func NewLagReporter(
	clusterRepo clusterrepo.Repo,
	clusterMeteringRepo billingrepo.ClusterMeteringRepo,
	billingReportRecord billingrepo.ReportRecordRepo,
) *LagReporter {
	return &LagReporter{
		clusterRepo:         clusterRepo,
		clusterMeteringRepo: clusterMeteringRepo,
		billingReportRecord: billingReportRecord,
	}
}

func (r *LagReporter) Report(ctx context.Context) {
	logger := tilog.WithContextS(ctx)

	if err := r.clusterMeteringLag(ctx); err != nil {
		logger.Warnf("cluster metering lag: %s", err)
	}

	if err := r.billingReportLag(ctx); err != nil {
		logger.Warnf("billing report lag: %s", err)
	}
}

func (r *LagReporter) clusterMeteringLag(ctx context.Context) bizerr.BizError {
	logger := tilog.WithContextL(ctx)
	clusterFilter := &clusterrepo.Filter{
		States: billedClusterStateSet,
	}
	clusters, err := r.clusterRepo.Query(ctx, clusterFilter)
	if err != nil {
		return err.Chain("query clusters")
	}

	for _, each := range clusters {
		var latest *entity.ClusterMetering
		latest, err = r.clusterMeteringRepo.LoadLatest(ctx, each.ClusterId)
		if err != nil {
			logger.Error("load cluster metering failed", zap.Uint64("clusterID", each.ClusterId), zap.Error(err))
			continue
		}

		labels := map[string]string{
			MetricsLblTenantId:    strconv.FormatUint(each.OrganizationId, 10),
			MetricsLblProjectId:   strconv.FormatUint(each.ProjectId, 10),
			MetricsLblClusterId:   strconv.FormatUint(each.ClusterId, 10),
			metricsLabelMeterType: metricsMeterTypeCluster,
		}
		MetricsMeterLag.With(labels).Set(time.Since(latest.StartTS).Seconds())
	}
	return nil
}

func (r *LagReporter) billingReportLag(ctx context.Context) bizerr.BizError {
	records, err := r.billingReportRecord.LoadUnsyncedRecords(ctx, 0, 1)
	if err != nil {
		return err
	}

	lag := float64(0)
	if len(records) > 0 {
		latest := records[0]
		lag = time.Since(latest.CreatedAt).Seconds()
	}
	MetricsReportLag.WithLabelValues().Set(lag)
	return nil
}
