package common

import (
	"context"
	"fmt"
	"time"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

// Representing different sub-stages in the upgrade process
const (
	PhaseCreateNodePools       = "create-nodepools"
	PhaseUpgradeNodePools      = "upgrade-nodepools"
	PhaseWaitNodesReady        = "wait-nodes-ready"
	PhaseWaitOverprovisioning  = "wait-overprovisioning"
	PhaseExtraTiDBScaleOut     = "extra-tidb-scale-out"
	PhaseRestartTiProxy        = "restart-tiproxy-mark"
	PhaseRestartTiProxyMigrate = "restart-tiproxy-migrate"
	PhaseRestartTiProxyReady   = "restart-tiproxy-ready"
	PhaseRestartStorage        = "restart-storage-mark"
	PhaseRestartStorageMigrate = "restart-storage-migrate"
	PhaseRestartStorageReady   = "restart-storage-ready"
	PhaseRestartTiDB           = "restart-tidb-mark"
	PhaseRestartTiDBMigrate    = "restart-tidb-migrate"
	PhaseRestartTiDBReady      = "restart-tidb-ready"
	PhaseWaitAllPodsMigrated   = "wait-all-pods-migrated"
	PhaseWaitTCReady           = "wait-tc-ready"
	PhaseUpgradeCDC            = "upgrade-cdc"
	PhaseAddResourceLabels     = "add-resource-labels"
)

// UpgradePhase identifies a fine-grained sub-phase within a node upgrade step.
type UpgradePhase string

func (p UpgradePhase) progressInfo(done, total int) string {
	if total > 0 {
		return fmt.Sprintf("%s=%d/%d", p, done, total)
	}
	return string(p)
}

// TrackProgress updates fine-grained upgrade progress on the current step.
// Step.Phases is a forward-only history: phases already visited are ignored to prevent
// regressions from overwriting progress state.
// Returns whether a status update is needed.
func TrackProgress(uo *cloudv1.UpgradeOperation, phase UpgradePhase, done, total int, now time.Time) bool {
	step := uo.Status.CurrentStep
	if step == nil {
		return false
	}

	changed := false
	n := len(step.Phases)

	// Phase regression: already visited this phase name — ignore.
	for i := 0; i < n-1; i++ {
		if step.Phases[i].Name == string(phase) {
			return false
		}
	}

	if n == 0 || step.Phases[n-1].Name != string(phase) {
		// Advance: close the current phase and open a new one.
		t := metav1.Time{Time: now}
		if n > 0 && step.Phases[n-1].EndTime == nil {
			step.Phases[n-1].EndTime = &t
		}
		step.Phases = append(step.Phases, cloudv1.StepPhase{
			Name:             string(phase),
			BeginTime:        &t,
			LastProgressTime: &t,
		})
		step.LastProgressTime = &t
		n++
		changed = true
	}

	cur := &step.Phases[n-1]

	// Update all idempotent fields together.
	pi := phase.progressInfo(done, total)
	if cur.ProgressInfo != pi || cur.Done != done || cur.Total != total {
		cur.ProgressInfo = pi
		cur.Done = done
		cur.Total = total
		changed = true
	}

	// Advance per-phase and step-level progress high-water marks when done count increases.
	if done > cur.MaxDone {
		cur.MaxDone = done
		t := metav1.Time{Time: now}
		cur.LastProgressTime = &t
		step.LastProgressTime = &t
		if done == total && cur.EndTime == nil {
			cur.EndTime = &t
		}
		changed = true
	}

	return changed
}

// TrackProgressAndUpdate persists progress when TrackProgress reports changes.
func TrackProgressAndUpdate(ctx context.Context, cli client.Client, uo *cloudv1.UpgradeOperation, phase UpgradePhase, done, total int, now time.Time) error {
	if !TrackProgress(uo, phase, done, total, now) {
		return nil
	}
	return cli.Status().Update(ctx, uo)
}
