package resources

import (
	"encoding/json"
	"strconv"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	infraconsts "github.com/tidbcloud/infra-provider/pkg/consts"
)

// UpgradeOperationProgressTimeout is the duration without phase progress before
// marking an UpgradeOperation as timed out.
var UpgradeOperationProgressTimeout = 20 * time.Minute

var (
	upgradeOperationGVR = schema.GroupVersionResource{
		Group:    "cloud.pingcap.com",
		Version:  "v1",
		Resource: "upgradeoperations",
	}

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

type upgradeOperation struct {
	defaultResource
}

var _ Interface = &upgradeOperation{}

func (uo upgradeOperation) HasDedicatedLabel() bool {
	return false
}

func (uo upgradeOperation) GVR() schema.GroupVersionResource {
	return upgradeOperationGVR
}

func (uo upgradeOperation) Desc() *prometheus.Desc {
	return upgradeOperationInfo
}

func (uo upgradeOperation) FilterOut(_ *unstructured.Unstructured) bool { return false }

func currentPhase(step *cloudv1.Step) *cloudv1.StepPhase {
	if step == nil {
		return nil
	}
	return step.ActivePhase()
}

func (uo upgradeOperation) Extract(obj *unstructured.Unstructured) prometheus.Metric {
	bytes, _ := obj.MarshalJSON()

	var upgradeOperationCR cloudv1.UpgradeOperation
	_ = json.Unmarshal(bytes, &upgradeOperationCR)

	var (
		missSchedule                       bool
		timeout                            bool
		curTarget, curBeginTime, beginTime string
		curPhaseName                       string
	)

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

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

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

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

	curPhase := currentPhase(upgradeOperationCR.Status.CurrentStep)
	if curPhase != nil {
		curPhaseName = curPhase.Name
		if curPhase.LastProgressTime != nil {
			if time.Since(curPhase.LastProgressTime.Time) > UpgradeOperationProgressTimeout {
				timeout = true
			}
		}
	}

	return prometheus.MustNewConstMetric(
		upgradeOperationInfo,
		prometheus.GaugeValue,
		1.0,
		upgradeOperationCR.Name,
		upgradeOperationCR.Namespace,
		upgradeOperationCR.Labels[infraconsts.TenantLabelKey],
		upgradeOperationCR.Labels[infraconsts.ProjectLabelKey],
		upgradeOperationCR.Spec.NetworkName,
		upgradeOperationCR.Spec.Schedule,
		strconv.FormatBool(upgradeOperationCR.Spec.Paused),
		curTarget,
		curBeginTime,
		strconv.Itoa(upgradeOperationCR.Status.NextSteps.Size()),
		strconv.Itoa(upgradeOperationCR.Status.FinishedSteps.Size()),
		beginTime,
		strconv.FormatBool(missSchedule),
		strconv.FormatBool(timeout),
		string(upgradeOperationCR.Status.Phase),
		curPhaseName,
	)
}
