// Package health implements GET /health and GET /ready probes.
package health

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/redis/go-redis/v9"
)

type Dependencies struct {
	DB    *pgxpool.Pool
	Redis *redis.Client
}

// Liveness always reports ok while the process is up. The env label exists
// so no human or agent ever mistakes a dev host for production: every liveness
// probe answers which environment it guards, in both the body and a header.
func Liveness(env string) http.HandlerFunc {
	if env == "" {
		env = "unknown"
	}
	return func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Environment", env)
		writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "env": env})
	}
}

// Readiness pings DB and Redis. Returns 503 (not an error envelope) when a
// dependency is unreachable, matching the probe contract.
func Readiness(deps Dependencies) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		ctx := r.Context()
		dbStatus := "connected"
		redisStatus := "connected"
		ready := true

		if deps.DB == nil {
			dbStatus = "unavailable"
			ready = false
		} else if err := ping(ctx, deps.DB.Ping); err != nil {
			dbStatus = "unavailable"
			ready = false
		}
		if deps.Redis == nil || ping(ctx, func(c context.Context) error {
			return deps.Redis.Ping(c).Err()
		}) != nil {
			redisStatus = "unavailable"
			ready = false
		}

		status := "ok"
		code := http.StatusOK
		if !ready {
			status = "degraded"
			code = http.StatusServiceUnavailable
		}
		writeJSON(w, code, map[string]string{
			"status":   status,
			"database": dbStatus,
			"redis":    redisStatus,
		})
	}
}

func ping(ctx context.Context, fn func(context.Context) error) error {
	pctx, cancel := context.WithTimeout(ctx, 2*time.Second)
	defer cancel()
	return fn(pctx)
}

func writeJSON(w http.ResponseWriter, status int, body any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(body)
}
