package obs

import (
	"fmt"
	"sync"
	"time"
)

// RED tracks Rate, Errors, and Duration per route — the metrics a gateway
// alerts on. Labels are bounded (route, status class); high-cardinality
// detail (trace ids, full paths) belongs in traces/logs, not metrics.
type RED struct {
	mu        sync.Mutex
	requests  map[string]int64      // "route|class" -> count
	durations map[string]*Histogram // route -> latency histogram
	retries   map[string]int64      // route -> retry count (the gw-06 amplification signal)
}

func NewRED() *RED {
	return &RED{
		requests:  map[string]int64{},
		durations: map[string]*Histogram{},
		retries:   map[string]int64{},
	}
}

// Record observes one completed request.
func (r *RED) Record(route string, statusCode int, d time.Duration) {
	class := fmt.Sprintf("%dxx", statusCode/100)
	r.mu.Lock()
	defer r.mu.Unlock()
	r.requests[route+"|"+class]++
	h, ok := r.durations[route]
	if !ok {
		h = NewHistogram(DefaultBounds)
		r.durations[route] = h
	}
	h.Observe(d.Seconds())
}

// AddRetry increments the retry counter for a route.
func (r *RED) AddRetry(route string) {
	r.mu.Lock()
	r.retries[route]++
	r.mu.Unlock()
}

// Rate returns total requests for a route (across all status classes).
func (r *RED) Rate(route string) int64 {
	r.mu.Lock()
	defer r.mu.Unlock()
	var n int64
	for _, class := range []string{"1xx", "2xx", "3xx", "4xx", "5xx"} {
		n += r.requests[route+"|"+class]
	}
	return n
}

// ErrorRatio returns the fraction of 5xx responses for a route.
func (r *RED) ErrorRatio(route string) float64 {
	r.mu.Lock()
	defer r.mu.Unlock()
	var total, errs int64
	for _, class := range []string{"1xx", "2xx", "3xx", "4xx", "5xx"} {
		total += r.requests[route+"|"+class]
	}
	errs = r.requests[route+"|5xx"]
	if total == 0 {
		return 0
	}
	return float64(errs) / float64(total)
}

// P99 returns the 99th-percentile latency for a route (seconds).
func (r *RED) P99(route string) float64 {
	r.mu.Lock()
	defer r.mu.Unlock()
	if h, ok := r.durations[route]; ok {
		return h.Quantile(0.99)
	}
	return 0
}

// RetryRatio returns retries/requests for a route — the early-warning
// signal for a retry storm (gw-06). Alert when this climbs.
func (r *RED) RetryRatio(route string) float64 {
	total := r.Rate(route)
	r.mu.Lock()
	defer r.mu.Unlock()
	if total == 0 {
		return 0
	}
	return float64(r.retries[route]) / float64(total)
}
