// Package reliability implements the reliability-engineering primitives an
// architect designs the platform's operability around: SLOs + error
// budgets, Google-SRE-style multi-window multi-burn-rate alerting (page on
// sustained burn, not on a blip), and bulkheads (concurrency isolation so
// one saturated dependency can't starve the rest). Stdlib-only.
package reliability

// SLO is a Service Level Objective: a target success ratio (e.g. 0.999).
// The ERROR BUDGET is the allowed failure fraction (1 - target) — the
// "currency" that governs how aggressively you ship vs stabilize.
type SLO struct {
	Target float64 // e.g. 0.999 = three nines
}

// ErrorBudget is the fraction of requests allowed to fail.
func (s SLO) ErrorBudget() float64 { return 1 - s.Target }

// ErrorRate over a window.
func ErrorRate(total, bad int) float64 {
	if total == 0 {
		return 0
	}
	return float64(bad) / float64(total)
}

// BurnRate is how fast a window is consuming the error budget:
// observedErrorRate / errorBudget. A burn rate of 1 exhausts the budget
// exactly over the SLO window; >1 is too fast; e.g. 14.4 burns ~2% of a
// 30-day budget in 1 hour (a paging condition).
func (s SLO) BurnRate(total, bad int) float64 {
	b := s.ErrorBudget()
	if b == 0 {
		return 0
	}
	return ErrorRate(total, bad) / b
}

// Severity is the alerting outcome.
type Severity int

const (
	OK Severity = iota
	Ticket
	Page
)

func (s Severity) String() string { return [...]string{"OK", "TICKET", "PAGE"}[s] }

// Window is request counts over some period.
type Window struct {
	Total int
	Bad   int
}

// Alert implements multi-window, multi-burn-rate alerting (Google SRE).
// It PAGES only when BOTH a long window (it's sustained, not a blip) AND
// a short window (it's still happening, not already resolved) exceed the
// page burn-rate threshold; it opens a TICKET on a slower sustained burn;
// otherwise OK. Requiring both windows is what stops a transient spike
// from paging.
func (s SLO) Alert(long, short Window, pageThreshold, ticketThreshold float64) Severity {
	lb := s.BurnRate(long.Total, long.Bad)
	sb := s.BurnRate(short.Total, short.Bad)
	if lb >= pageThreshold && sb >= pageThreshold {
		return Page
	}
	if lb >= ticketThreshold && sb >= ticketThreshold {
		return Ticket
	}
	return OK
}
