package daemon

import (
	"context"
	"encoding/json"
	"log/slog"
	"net/http"
	"time"
)

// handleHealthz reports liveness. It does NOT check dependencies — by design,
// a process that can answer this is "alive" even if its DB is wedged. Use
// /readyz for dependency health.
func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"status": "alive",
		"time":   time.Now().UTC().Format(time.RFC3339),
	})
}

// handleReadyz reports readiness. Returns 503 if either:
//   - MarkReady has not been called, OR
//   - the SQLite database cannot be pinged within 2s.
//
// Distinguishing these cases in the response body helps operators triage.
func (s *Server) handleReadyz(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
	defer cancel()

	if !s.ready.Load() {
		writeJSON(w, http.StatusServiceUnavailable, map[string]any{
			"status": "not_ready",
			"reason": "daemon not marked ready",
		})
		return
	}
	if err := s.db.PingContext(ctx); err != nil {
		s.log.Warn("readyz db ping failed",
			slog.String("component", "daemon"),
			slog.String("error", err.Error()))
		writeJSON(w, http.StatusServiceUnavailable, map[string]any{
			"status": "not_ready",
			"reason": "db ping failed",
		})
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"status": "ready",
		"db":     "ok",
	})
}

// handleStatus returns a small diagnostic JSON blob. Cheap to call; does
// NOT touch the database unless we want a DB status check, in which case
// the ping is bounded by 2s.
func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}
	ctx, cancel := context.WithTimeout(r.Context(), 2*time.Second)
	defer cancel()

	dbStatus := "ok"
	if err := s.db.PingContext(ctx); err != nil {
		dbStatus = "error"
		s.log.Warn("status db ping failed",
			slog.String("component", "daemon"),
			slog.String("error", err.Error()))
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"version":   Version,
		"phase":     "5-health-checks",
		"milestone": "v0.1",
		"db":        dbStatus,
		"ready":     s.ready.Load(),
		"time":      time.Now().UTC().Format(time.RFC3339),
	})
}

// writeJSON encodes body as JSON with the given status code.
// Errors during encoding are logged but not surfaced — we cannot write
// another header after the response has started.
func writeJSON(w http.ResponseWriter, code int, body any) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(code)
	_ = json.NewEncoder(w).Encode(body)
}

// writeError emits a uniform error envelope: {"error": "<message>"}.
func writeError(w http.ResponseWriter, code int, msg string) {
	writeJSON(w, code, map[string]string{"error": msg})
}

// loggingMiddleware wraps the mux with a structured access log. It does
// NOT log request/response bodies (could contain secrets); just method,
// path, status, and duration.
func loggingMiddleware(log *slog.Logger, next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		ww := &statusRecorder{ResponseWriter: w, status: 200}
		next.ServeHTTP(ww, r)
		log.Info("http",
			slog.String("method", r.Method),
			slog.String("path", r.URL.Path),
			slog.Int("status", ww.status),
			slog.Duration("dur", time.Since(start)),
			slog.String("remote", r.RemoteAddr),
		)
	})
}

type statusRecorder struct {
	http.ResponseWriter
	status int
}

func (s *statusRecorder) WriteHeader(code int) {
	s.status = code
	s.ResponseWriter.WriteHeader(code)
}
