// Package rollout models the migration ladder a senior engineer runs to
// ship a risky gateway change safely: shadow -> canary -> ramp -> soak,
// with automated canary analysis (compare canary vs baseline) gating each
// step and automatic rollback on an SLO breach. Stdlib-only and
// deterministic.
package rollout

import "fmt"

// Metrics are the golden signals compared between the canary (new path)
// and the baseline (old path / control group). Latency is seconds.
type Metrics struct {
	ErrorRate float64
	P99       float64
}

// Decision is the outcome of automated canary analysis.
type Decision int

const (
	Promote Decision = iota
	Rollback
)

func (d Decision) String() string {
	if d == Rollback {
		return "ROLLBACK"
	}
	return "PROMOTE"
}

// Config sets the tolerances for canary analysis (the SLO gate).
type Config struct {
	MaxErrorRateDelta float64 // canary error rate may exceed baseline by at most this
	MaxP99Ratio       float64 // canary p99 may be at most this × baseline p99
}

// DefaultConfig: canary may add up to 0.5% absolute error rate and be up
// to 1.2× the baseline tail.
var DefaultConfig = Config{MaxErrorRateDelta: 0.005, MaxP99Ratio: 1.2}

// Analyze compares canary to baseline and returns the gate decision —
// statistically simplistic here; production uses Kayenta-style analysis.
func Analyze(baseline, canary Metrics, cfg Config) Decision {
	if canary.ErrorRate > baseline.ErrorRate+cfg.MaxErrorRateDelta {
		return Rollback
	}
	if baseline.P99 > 0 && canary.P99 > baseline.P99*cfg.MaxP99Ratio {
		return Rollback
	}
	return Promote
}

// Stage is one rung of the ladder. Percent==0 is the shadow stage
// (mirrored traffic, no user impact); Percent==100 is full cutover.
type Stage struct {
	Name    string
	Percent int
}

// DefaultLadder: shadow, then a 1%→5%→25%→50%→100% ramp.
var DefaultLadder = []Stage{
	{"shadow", 0},
	{"canary", 1},
	{"ramp", 5},
	{"ramp", 25},
	{"ramp", 50},
	{"full", 100},
}

// MetricsFunc supplies (baseline, canary) metrics observed at a stage.
// In a real rollout these come from gw-11; here a simulation/test
// provides them.
type MetricsFunc func(stage Stage) (baseline, canary Metrics)

// Result is the outcome of a ladder run.
type Result struct {
	Reached    int  // highest traffic percent successfully promoted to
	RolledBack bool // true if a stage breached the SLO gate
	History    []string
}

// Run drives the ladder: the shadow stage proceeds risk-free; each
// traffic-bearing stage is gated by Analyze and rolls back on breach,
// leaving Reached at the last safely-promoted percent (the old path stays
// warm, so rollback is instant).
func Run(ladder []Stage, cfg Config, mf MetricsFunc) Result {
	res := Result{}
	for _, st := range ladder {
		if st.Percent == 0 {
			res.History = append(res.History, fmt.Sprintf("%-7s mirrored traffic; no user impact", st.Name))
			continue
		}
		baseline, canary := mf(st)
		d := Analyze(baseline, canary, cfg)
		res.History = append(res.History, fmt.Sprintf(
			"%-7s %3d%%  baseErr=%.3f canErr=%.3f baseP99=%.3f canP99=%.3f -> %s",
			st.Name, st.Percent, baseline.ErrorRate, canary.ErrorRate, baseline.P99, canary.P99, d))
		if d == Rollback {
			res.RolledBack = true
			res.History = append(res.History, fmt.Sprintf("        auto-rollback to %d%% (old path still warm)", res.Reached))
			return res
		}
		res.Reached = st.Percent
	}
	return res
}
