package server

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

	"github.com/go-chi/chi/v5"

	"github.com/operator/command-center/internal/decisions"
	"github.com/operator/command-center/internal/intelligence"
)

// IntelligenceDeps wraps the Phase 7 dependencies passed in via server.Options.
type IntelligenceDeps struct {
	Engine    *intelligence.Engine
	Decisions *decisions.Store
}

func (s *Server) handleRatioVelocity(w http.ResponseWriter, r *http.Request) {
	if s.sqlite == nil {
		http.Error(w, "sqlite required", http.StatusServiceUnavailable)
		return
	}
	window := 24 * time.Hour
	if v := r.URL.Query().Get("range"); v != "" {
		window = rangeToDuration(v)
	}
	out, err := intelligence.ComputeAll(r.Context(), s.sqlite, window)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "velocities": out})
}

func (s *Server) handleHRRisk(w http.ResponseWriter, r *http.Request) {
	if s.sqlite == nil {
		http.Error(w, "sqlite required", http.StatusServiceUnavailable)
		return
	}
	hrs := 24.0
	if v := r.URL.Query().Get("within"); v != "" {
		// parse "24h" / "7d" / "30d" → hours
		switch v {
		case "24h":
			hrs = 24
		case "7d":
			hrs = 7 * 24
		case "30d":
			hrs = 30 * 24
		default:
			if f, err := strconv.ParseFloat(v, 64); err == nil {
				hrs = f
			}
		}
	}
	out, err := intelligence.FindAtRisk(r.Context(), s.sqlite, hrs, 432000)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "at_risk": out})
}

func (s *Server) handleDeadSwarms(w http.ResponseWriter, r *http.Request) {
	if s.sqlite == nil {
		http.Error(w, "sqlite required", http.StatusServiceUnavailable)
		return
	}
	hrs := 72.0
	if v := r.URL.Query().Get("threshold_hours"); v != "" {
		if f, err := strconv.ParseFloat(v, 64); err == nil {
			hrs = f
		}
	}
	out, err := intelligence.Find(r.Context(), s.sqlite, hrs)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "dead_swarms": out})
}

func (s *Server) handleDiskForecast(w http.ResponseWriter, r *http.Request) {
	if s.sqlite == nil {
		http.Error(w, "sqlite required", http.StatusServiceUnavailable)
		return
	}
	// Phase 7 takes capacity/current as query params (or 0 = no forecast).
	// Phase 15 wires this into operator config + an OS probe.
	cap, _ := strconv.ParseInt(r.URL.Query().Get("capacity_bytes"), 10, 64)
	cur, _ := strconv.ParseInt(r.URL.Query().Get("current_bytes"), 10, 64)
	if cap == 0 {
		writeJSON(w, http.StatusOK, map[string]any{
			"note": "Provide capacity_bytes (and optionally current_bytes) as query params to compute a forecast. Phase 15 wires these into operator config.",
		})
		return
	}
	out, err := intelligence.Forecast(r.Context(), s.sqlite, intelligence.ForecastConfig{
		CapacityBytes: cap,
		CurrentBytes:  cur,
	})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJSON(w, http.StatusOK, out)
}

func (s *Server) handleRecommendations(w http.ResponseWriter, r *http.Request) {
	if s.intelligence == nil {
		http.Error(w, "intelligence not configured", http.StatusServiceUnavailable)
		return
	}
	recs, err := s.intelligence.Engine.Generate(r.Context())
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	if recs == nil {
		// Empty slice instead of nil — JSON-marshals as `[]` not `null` so
		// the frontend's `.slice(0, n)` and `.map(...)` calls don't crash
		// on the empty-state path. Generate() returns nil when no rules
		// fire (common on a fresh install with no snapshot data).
		recs = []intelligence.Recommendation{}
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(recs), "recommendations": recs})
}

// Decision endpoints.

func (s *Server) handleListDecisions(w http.ResponseWriter, r *http.Request) {
	if s.intelligence == nil || s.intelligence.Decisions == nil {
		http.Error(w, "decisions not configured", http.StatusServiceUnavailable)
		return
	}
	action := r.URL.Query().Get("action")
	limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
	out, err := s.intelligence.Decisions.List(r.Context(), action, limit)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"count": len(out), "decisions": out})
}

func (s *Server) handleGetDecision(w http.ResponseWriter, r *http.Request) {
	if s.intelligence == nil || s.intelligence.Decisions == nil {
		http.Error(w, "decisions not configured", http.StatusServiceUnavailable)
		return
	}
	id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
	if err != nil {
		http.Error(w, "bad id", http.StatusBadRequest)
		return
	}
	d, err := s.intelligence.Decisions.Get(r.Context(), id)
	if err == decisions.ErrNotFound {
		http.Error(w, "not found", http.StatusNotFound)
		return
	}
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// Expand provenance for the detail view.
	var prov any
	_ = json.Unmarshal([]byte(d.ProvenanceJSON), &prov)
	writeJSON(w, http.StatusOK, map[string]any{
		"decision":   d,
		"provenance": prov,
	})
}

func (s *Server) handleDecisionAction(action string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if s.intelligence == nil || s.intelligence.Decisions == nil {
			http.Error(w, "decisions not configured", http.StatusServiceUnavailable)
			return
		}
		id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64)
		if err != nil {
			http.Error(w, "bad id", http.StatusBadRequest)
			return
		}
		if err := s.intelligence.Decisions.SetAction(r.Context(), id, action); err == decisions.ErrNotFound {
			http.Error(w, "not found", http.StatusNotFound)
			return
		} else if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		writeJSON(w, http.StatusOK, map[string]any{"id": id, "action": action})
	}
}

func rangeToDuration(s string) time.Duration {
	switch s {
	case "24h", "":
		return 24 * time.Hour
	case "7d":
		return 7 * 24 * time.Hour
	case "30d":
		return 30 * 24 * time.Hour
	}
	return 24 * time.Hour
}
