// Package observability is the shared logging + metrics + OTel stub.
//
// M0: slog (stdlib structured logging, JSON output) and Prometheus
// metrics via promhttp. OTel is a stub — we wire it in M9.
//
// All services should call Init() in main and then use the returned
// logger and the package-level metrics.
package observability

import (
	"context"
	"log/slog"
	"net/http"
	"os"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
)

// Init returns a slog.Logger configured for the env. JSON in non-dev
// for log shippers, text in dev for human eyes.
func Init(env, level, serviceName string) *slog.Logger {
	lvl := parseLevel(level)
	opts := &slog.HandlerOptions{Level: lvl}

	var h slog.Handler
	if env == "dev" {
		h = slog.NewTextHandler(os.Stdout, opts)
	} else {
		h = slog.NewJSONHandler(os.Stdout, opts)
	}

	logger := slog.New(h).With(
		slog.String("service", serviceName),
		slog.String("env", env),
	)
	slog.SetDefault(logger)
	return logger
}

func parseLevel(s string) slog.Level {
	switch strings.ToLower(s) {
	case "debug":
		return slog.LevelDebug
	case "warn":
		return slog.LevelWarn
	case "error":
		return slog.LevelError
	default:
		return slog.LevelInfo
	}
}

// MetricsHandler returns an http.Handler that serves Prometheus
// metrics from the given registry.
func MetricsHandler(reg *prometheus.Registry) http.Handler {
	return promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg})
}

// HealthHandler is the standard /health endpoint. Liveness only —
// readiness (can serve traffic) is a separate concern, added per
// service as it comes online.
func HealthHandler(serviceName string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"status":"ok","service":"` + serviceName + `"}`))
	})
}

// CtxWithTrace is a stub for M9 when OTel is wired in. For now
// it just attaches the request id (or generates one) so logs can
// be correlated.
func CtxWithTrace(ctx context.Context, traceID string) context.Context {
	if traceID == "" {
		// tiny random — not crypto-strong, but unique enough
		traceID = newTraceID()
	}
	return context.WithValue(ctx, traceKey{}, traceID)
}

// TraceFromCtx returns the trace id, or "" if none.
func TraceFromCtx(ctx context.Context) string {
	v, _ := ctx.Value(traceKey{}).(string)
	return v
}

type traceKey struct{}

func newTraceID() string {
	b := make([]byte, 8)
	if _, err := readRandom(b); err != nil {
		return "0"
	}
	return hexEncode(b)
}
