package collectors

import (
	"context"
	"fmt"
	"strconv"
	"time"

	"github.com/go-logr/logr"
	"github.com/prometheus/client_golang/prometheus"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	uocommon "github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/upgradeoperation/common"
)

var (
	upgradeOperationInfo = prometheus.NewDesc(
		"regional_server_upgradeoperation_info",
		"Information about UpgradeOperation CR.",
		[]string{
			"name",
			"namespace",
			"tenant_id",
			"project_id",
			"appbox_name",
			"schedule",
			"paused",
			"current_target",
			"current_step_begin_time",
			"next_steps",
			"finished_steps",
			"begin_time",
			"miss_schedule",
			"timeout",
			"phase",
			"current_phase",
		}, nil)

	upgradeOperationMaintenancePending = prometheus.NewDesc(
		"regional_server_uo_maintenance_pending",
		"UpgradeOperation step maintenance annotation waiting for DevOps response.",
		[]string{"name", "namespace", "tenant_id", "project_id", "step_index", "kind", "cluster_id"},
		nil)
)

type upgradeOperation struct {
	cli client.Client
	log logr.Logger
}

var _ prometheus.Collector = &upgradeOperation{}

func (m *upgradeOperation) Describe(ch chan<- *prometheus.Desc) {
	ch <- upgradeOperationInfo
	ch <- upgradeOperationMaintenancePending
}

func (m *upgradeOperation) Collect(ch chan<- prometheus.Metric) {
	var list v1alpha1.UpgradeOperationList
	if err := m.cli.List(context.TODO(), &list); err != nil {
		m.log.Error(err, "fetching upgradeOperationList")
		return
	}

	for _, uo := range list.Items {
		var (
			missSchedule                       bool
			timeout                            bool
			curTarget, curBeginTime, beginTime string
			currentPhase                       string
		)

		now := time.Now()
		// The schedule is reached but the phase is still pending.
		schedule, _ := time.Parse(v1alpha1.UpgradeOperationScheduleLayout, uo.Spec.Schedule)
		if now.After(schedule) && uo.Status.Phase == v1alpha1.UpgradeOperationPhasePending {
			missSchedule = true
		}

		// The actual duration is greater than the desired duration.
		if uo.Status.BeginTime != nil && now.Sub(uo.Status.BeginTime.Time) > uo.Spec.Duration.Duration {
			timeout = true
		}

		if uo.Status.CurrentStep != nil {
			curTarget = string(uo.Status.CurrentStep.Target)
			if uo.Status.CurrentStep.BeginTime != nil {
				curBeginTime = uo.Status.CurrentStep.BeginTime.String()
			}
		}

		if uo.Status.BeginTime != nil {
			beginTime = uo.Status.BeginTime.String()
		}

		currentPhase = ""
		if uo.Status.CurrentStep != nil {
			if active := uo.Status.CurrentStep.ActivePhase(); active != nil {
				currentPhase = active.Name
			}
		}
		ch <- prometheus.MustNewConstMetric(
			upgradeOperationInfo,
			prometheus.GaugeValue,
			1.0,
			uo.Name,
			uo.Namespace,
			uo.Labels[v1alpha1.TagTenantKey],
			uo.Labels[v1alpha1.TagProjectKey],
			uo.Spec.AppBoxName,
			uo.Spec.Schedule,
			strconv.FormatBool(uo.Spec.Paused),
			curTarget,
			curBeginTime,
			strconv.Itoa(uo.Status.NextSteps.Size()),
			strconv.Itoa(uo.Status.FinishedSteps.Size()),
			beginTime,
			strconv.FormatBool(missSchedule),
			strconv.FormatBool(timeout),
			string(uo.Status.Phase),
			currentPhase,
		)

		m.collectMaintenanceStuckMetrics(ch, &uo)
	}
}

func (m *upgradeOperation) collectMaintenanceStuckMetrics(ch chan<- prometheus.Metric, uo *v1alpha1.UpgradeOperation) {
	if !uo.IsClusterMaintenanceMode() {
		return
	}
	for key, raw := range uo.Annotations {
		if !uocommon.IsMaintenanceAnnotationKey(key) {
			continue
		}
		clusterID, done, err := uocommon.ParseMaintenanceAnnotation(raw)
		if err != nil {
			m.log.Error(err, "failed to parse maintenance annotation", "key", key, "raw", raw, "upgradeOperation", uo.Name)
			continue
		}
		if clusterID == "" || done {
			continue
		}
		stepIndex, ok := uocommon.MaintenanceAnnotationStepIndex(key)
		if !ok {
			m.log.Error(fmt.Errorf("failed to get step index from maintenance annotation key"), "key", key, "upgradeOperation", uo.Name)
			continue
		}
		ch <- prometheus.MustNewConstMetric(
			upgradeOperationMaintenancePending,
			prometheus.GaugeValue,
			1.0,
			uo.Name,
			uo.Namespace,
			uo.Labels[v1alpha1.TagTenantKey],
			uo.Labels[v1alpha1.TagProjectKey],
			strconv.Itoa(stepIndex),
			uocommon.MaintenanceAnnotationKind(key),
			clusterID,
		)
	}
}
