package componentnodevcpu

import (
	"context"
	"slices"
	"strings"
	"time"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/tidbcloud/cluster-service-ng/internal/dataplane-exporter/pkg/diagnostics"
)

type Reader interface {
	Read(ctx context.Context) (Snapshot, error)
}

type Exporter struct {
	reader Reader
}

func NewExporter(reader Reader) *Exporter {
	return &Exporter{reader: reader}
}

func (e *Exporter) Name() string {
	return "component-node-vcpu"
}

func (e *Exporter) Register(ctx context.Context, reg prometheus.Registerer) {
	reg.MustRegister(newCollector(ctx, e.reader))
}

type collector struct {
	// The scrape request context has to live on the collector because the
	// Prometheus Collector interface does not pass context into Collect.
	ctx               context.Context
	reader            Reader
	mainDesc          *prometheus.Desc
	nodesDesc         *prometheus.Desc
	mismatchNodesDesc *prometheus.Desc
	successDesc       *prometheus.Desc
	durationDesc      *prometheus.Desc
}

func newCollector(ctx context.Context, reader Reader) *collector {
	return &collector{
		ctx:    ctx,
		reader: reader,
		mainDesc: prometheus.NewDesc(
			diagnostics.MetricComponentNodeVCPU,
			"Node vCPU attributed to dataplane components.",
			[]string{"tenant", "resource_pool", "component", "cluster", "node_class"},
			nil,
		),
		nodesDesc: prometheus.NewDesc(
			diagnostics.MetricComponentNodeVCPUNodes,
			"Node attribution counts by state and node class.",
			[]string{"state", "node_class"},
			nil,
		),
		mismatchNodesDesc: prometheus.NewDesc(
			diagnostics.MetricComponentNodeVCPUCPUMismatchNodes,
			"Worker node counts whose node-spec CPU mismatches capacity CPU.",
			[]string{"node_class"},
			nil,
		),
		successDesc: prometheus.NewDesc(
			diagnostics.MetricComponentNodeVCPUSuccess,
			"Whether the component node vCPU scrape succeeded.",
			nil,
			nil,
		),
		durationDesc: prometheus.NewDesc(
			diagnostics.MetricComponentNodeVCPUDuration,
			"Duration of the component node vCPU scrape in seconds.",
			nil,
			nil,
		),
	}
}

func (c *collector) Describe(ch chan<- *prometheus.Desc) {
	ch <- c.mainDesc
	ch <- c.nodesDesc
	ch <- c.mismatchNodesDesc
	ch <- c.successDesc
	ch <- c.durationDesc
}

func (c *collector) Collect(ch chan<- prometheus.Metric) {
	started := time.Now()
	snapshot, err := c.reader.Read(c.ctx)
	success := 1.0
	if err != nil {
		success = 0
	} else {
		for _, sample := range snapshot.Samples {
			ch <- prometheus.MustNewConstMetric(
				c.mainDesc,
				prometheus.GaugeValue,
				sample.VCPU,
				sample.Tenant,
				sample.ResourcePool,
				sample.Component,
				sample.Cluster,
				sample.NodeClass,
			)
		}

		snapshotDiagnostics := slices.Clone(snapshot.Diagnostics)
		slices.SortFunc(snapshotDiagnostics, func(a, b Diagnostic) int {
			if cmp := strings.Compare(a.State, b.State); cmp != 0 {
				return cmp
			}
			return strings.Compare(a.NodeClass, b.NodeClass)
		})
		for _, diagnostic := range snapshotDiagnostics {
			ch <- prometheus.MustNewConstMetric(c.nodesDesc, prometheus.GaugeValue, diagnostic.Nodes, diagnostic.State, diagnostic.NodeClass)
		}

		snapshotMismatchDiagnostics := slices.Clone(snapshot.MismatchDiagnostics)
		slices.SortFunc(snapshotMismatchDiagnostics, func(a, b MismatchDiagnostic) int {
			return strings.Compare(a.NodeClass, b.NodeClass)
		})
		for _, mismatchDiagnostic := range snapshotMismatchDiagnostics {
			ch <- prometheus.MustNewConstMetric(
				c.mismatchNodesDesc,
				prometheus.GaugeValue,
				mismatchDiagnostic.Nodes,
				mismatchDiagnostic.NodeClass,
			)
		}
	}

	ch <- prometheus.MustNewConstMetric(c.successDesc, prometheus.GaugeValue, success)
	ch <- prometheus.MustNewConstMetric(c.durationDesc, prometheus.GaugeValue, time.Since(started).Seconds())
}
