package metrics

import (
	"github.com/prometheus/client_golang/prometheus"
	k8smetrics "sigs.k8s.io/controller-runtime/pkg/metrics"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-provider/pkg/metrics/controller"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

const (
	healthyStatus   = 1
	unhealthyStatus = -1
)

var (
	ClusterCreationDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "infra",
			Subsystem: "cluster",
			Name:      "create_stage_duration",
			Help:      "the time that used in each cluster create stage",
			Buckets:   prometheus.ExponentialBuckets(10, 2, 6), // 10s~640s
		}, []string{"stage"})

	NetworkCreationDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "infra",
			Subsystem: "network",
			Name:      "create_stage_duration",
			Help:      "the time that used in each network create stage",
			Buckets:   prometheus.ExponentialBuckets(10, 2, 7), // 10s~1280s
		}, []string{"stage"})

	O11yInfraAPIRequestCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "o11y",
			Name:      "api_request_total",
			Help:      "How many times eks-provider request o11y infra API.",
		},
		[]string{
			"region",
			"tenantID",
			"projectID",
			"clusterID",
			"o11yClusterID",
			"method", // create or get or delete
			"message",
		},
	)

	O11yComponentHealthStatus = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: "infra",
			Subsystem: "o11y",
			Name:      "component_health_status",
			Help:      "1 represents healthy, -1 represents unhealthy",
		},
		[]string{
			"cluster", // eks cluster name
			"region",
			"namespace",
			"name", // component name
		},
	)

	ClusterFirstPassStageSeconds = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "infra",
			Subsystem: "cluster",
			Name:      "first_pass_stage_seconds",
			Help:      "the time that used when first pass the stage of cluster creation",
			Buckets:   prometheus.ExponentialBucketsRange(1, 3600, 20), // 1s~3600s
		}, []string{"stage"})

	NetworkFirstPassStageSeconds = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "infra",
			Subsystem: "network",
			Name:      "first_pass_stage_seconds",
			Help:      "the time that used when first pass the stage of network creation",
			Buckets:   prometheus.ExponentialBucketsRange(1, 3600, 20), // 1s~3600s
		}, []string{"stage"})

	ConfigRevisionRefIsNil = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: "infra",
			Subsystem: "config",
			Name:      "revision_ref_is_nil",
			Help:      "RevisionRef is nil in the Infra Cluster CRD",
		}, []string{"cluster_id"})

	ConnectToClusterProxyFailed = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "provider",
			Name:      "connect_to_cluster_proxy_failed",
			Help:      "How many times infra provider connect to cluster proxy failed.",
		}, []string{"project_id", "region", "message"},
	)

	ControllerPanic = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "provider",
			Name:      "controller_panic",
			Help:      "How many times infra provider controller panics.",
		}, []string{"controller", "region"},
	)

	ReconcileDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "infra",
			Subsystem: "controller",
			Name:      "provider_reconcile_duration_ms",
			Help:      "the time that used in each controller reconcile",
			Buckets:   prometheus.ExponentialBucketsRange(1, 60000, 20), // 1ms~1min
		}, []string{"controller", "provider", "region"})

	ImportFailCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "import",
			Name:      "import_fail",
			Help:      "How many times infra import controller failed.",
		}, []string{"provider", "region", "sence"},
	)
	ControllerTaskDuration = prometheus.NewHistogramVec(
		prometheus.HistogramOpts{
			Namespace: "infra",
			Subsystem: "controller",
			Name:      "controller_task_duration_ms",
			Help:      "the time that used in ecache controller task.",
			Buckets:   prometheus.ExponentialBucketsRange(1, 60000, 20), // 1ms~1min
		}, []string{"controller", "provider", "region", "task", "step", "ok"},
	)

	DMFailCounter = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "dm",
			Name:      "dm_fail",
			Help:      "How many times infra dm controller failed.",
		}, []string{"provider", "region", "sence"},
	)
	ControllerPrereleaseJob = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: "infra",
			Subsystem: "controller",
			Name:      "prerelease_job",
			Help:      "The result of prerelease job of cluster-controller",
		}, []string{"controller", "provider", "region", "result"})
	ClusterDryrun = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: "infra",
			Subsystem: "cluster",
			Name:      "dryrun",
			Help:      "The result of cluster CR dryrun",
		}, []string{"provider", "region", "name", "namespace", "tenant_id", "project_id", "version", "status", "paused", "result"})

	ClusterReconcileError = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "cluster",
			Name:      "reconcile_error",
			Help:      "How many times infra cluster controller failed.",
		}, []string{"provider", "region", "name", "namespace", "tenant_id", "project_id", "version", "status", "paused"})
	// ScaleProtectionMissingNode is 1 if the given node is missing scale-in protection, 0 otherwise.
	// Labels include cluster status/node/instance/component and detail flags so alerting can pinpoint the issue.
	ScaleProtectionMissingNode = prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace: "infra",
			Subsystem: "cluster",
			Name:      "scale_protection_missing_node",
			Help:      "1 if this node should have scale-in protection but does not (annotation or AWS), 0 otherwise. Includes node, instance, component and missing flags.",
		}, []string{
			"provider",
			"region",
			"name",      // cluster name
			"namespace", // cluster namespace
			"tenant_id",
			"project_id",
			"status",                  // cluster status/phase
			"node",                    // k8s node name
			"instance_id",             // AWS instance ID
			"component",               // TiDB component type
			"node_annotation_missing", // "true"/"false"
			"aws_missing",             // "true"/"false"
		})
	// ScaleProtectionCheckSkipped counts per-cluster (and optionally per-node) check runs
	// that were skipped early due to an internal error (for example, failing to list nodes or pods).
	ScaleProtectionCheckSkipped = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "cluster",
			Name:      "scale_protection_check_skipped",
			Help:      "Total number of scale protection checks skipped for a cluster due to internal errors, labelled by cluster status, node and reason.",
		}, []string{
			"provider",
			"region",
			"name",
			"namespace",
			"tenant_id",
			"project_id",
			"status",
			"reason",
			"node",
		})
	// PrivateLinkPurgeMetadataTotal counts each invocation of PurgePrivateLinkMetadata,
	// with a result label of "success", "dry_run", "precondition_failed", "conflict" or "error".
	PrivateLinkPurgeMetadataTotal = prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace: "infra",
			Subsystem: "privatelinkservice",
			Name:      "purge_metadata_total",
			Help:      "Total number of PurgePrivateLinkMetadata invocations, labelled by provider and result.",
		}, []string{"provider", "result"})
)

func RegisterMetrics() {
	k8smetrics.Registry.MustRegister(
		ClusterCreationDuration,
		O11yInfraAPIRequestCounter,
		O11yComponentHealthStatus,
		controller.ReconcileDuration,
		ClusterFirstPassStageSeconds,
		NetworkFirstPassStageSeconds,
		ConfigRevisionRefIsNil,
		ConnectToClusterProxyFailed,
		ControllerPanic,
		ReconcileDuration,
		ImportFailCounter,
		ControllerTaskDuration,
		DMFailCounter,
		ControllerPrereleaseJob,
		ClusterDryrun,
		ClusterReconcileError,
		ScaleProtectionMissingNode,
		ScaleProtectionCheckSkipped,
		PrivateLinkPurgeMetadataTotal,
	)
}

func UpdateO11yComponentHealthStatus(network *cloudv1.Network, ns, componentName string, isDoneState bool) {
	eksName := namer.LegacyBuildClusterNameByNetwork(network)
	status := O11yComponentHealthStatus.WithLabelValues(eksName, network.Spec.Region, ns, componentName)
	if isDoneState {
		status.Set(healthyStatus)
	} else {
		status.Set(unhealthyStatus)
	}
}
