package metric

import (
	"context"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/internal/domain/constant"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/repo"
	"go.opentelemetry.io/otel"
	"go.uber.org/zap"
)

const (
	maxExportRunningTime = 60 * time.Minute
)

var ExportTracer = otel.Tracer("export-metric-collector")

var (
	ExportStatus = prometheus.NewDesc(
		prometheus.BuildFQName(MetricNamespace, "export", "status"),
		"Status of export job",
		[]string{"status", "cluster_id", "export_id"},
		nil)

	ExportRunningTime = prometheus.NewDesc(
		prometheus.BuildFQName(MetricNamespace, "export", "running_duration_seconds"),
		"Duration in seconds to running export job",
		[]string{"cluster_id", "export_id"},
		nil)

	ExportFailedGauge = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: MetricNamespace,
			Subsystem: "export",
			Name:      "failed_number",
			Help:      "Failed export",
		},
		[]string{"cluster_id", "export_id", "reason"},
	)
	ExportEventHandlerCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: MetricNamespace,
			Subsystem: "export",
			Name:      "event_handle_total",
			Help:      "Counter for export event handle",
		},
		[]string{"event_id", "cluster_id", "event_type", "status"},
	)
	ExportSizeGuage = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: MetricNamespace,
			Subsystem: "export",
			Name:      "size",
			Help:      "The size in MB for export",
		},
		[]string{"export_id", "is_blocked"},
	)
)

func init() {
	prometheus.MustRegister(ExportFailedGauge)
	prometheus.MustRegister(ExportEventHandlerCounter)
	prometheus.MustRegister(ExportSizeGuage)
}

type ExportMetricCollector struct {
	exportRepo repo.ExportRepo
}

func NewExportMetricCollector(exportRepo repo.ExportRepo) *ExportMetricCollector {
	return &ExportMetricCollector{
		exportRepo: exportRepo,
	}
}

func (c *ExportMetricCollector) Describe(ch chan<- *prometheus.Desc) {
	ch <- ExportRunningTime
}

func (c *ExportMetricCollector) Collect(ch chan<- prometheus.Metric) {
	ctx := context.Background()
	var err error
	ctx, span := ExportTracer.Start(ctx, "MetricsCollector.Collect")
	defer func() {
		tracing.EndSpan(span, err)
	}()

	log.Info(ctx, "start to collect Export metric")

	stateCount, err := c.exportRepo.GroupByState(ctx)
	if err != nil {
		log.Error(ctx, "failed to count export", zap.Error(err))
		return
	}
	for _, sc := range stateCount {
		ch <- prometheus.MustNewConstMetric(
			ExportStatus,
			prometheus.GaugeValue,
			float64(sc.Count),
			string(sc.State),
			"",
			"",
		)
	}

	runningExps, err := c.exportRepo.ListByFilter(ctx, entity.ExportFilter{
		State: constant.ExportStateRunning,
	})
	if err != nil {
		log.Error(ctx, "failed to list running export", zap.Error(err))
		return
	}
	for _, ex := range runningExps {
		duration := time.Since(*ex.CreatedAt)
		if duration > maxExportRunningTime {
			ch <- prometheus.MustNewConstMetric(
				ExportRunningTime,
				prometheus.GaugeValue,
				duration.Seconds(),
				ex.ClusterID,
				ex.ID,
			)
			log.Warn(ctx, "export running too long", zap.String("exportID", ex.ID))
		}
	}
}
