package handler

import (
	"encoding/json"
	"io"
	"net/http"
	"net/http/pprof"
	"strings"

	"github.com/elfoundation/hatch/internal/store"
	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

// Handler groups all HTTP handlers and their shared dependencies.
type Handler struct {
	Repo  store.Repository
	Debug bool
}

// New creates a new Handler with the given store.
func New(repo store.Repository) *Handler {
	return &Handler{Repo: repo}
}

// RegisterRoutes mounts all routes on the given chi router.
func (h *Handler) RegisterRoutes(r chi.Router) {
	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)

	// Health check.
	r.Get("/healthz", Healthz)
	r.Get("/readyz", h.Readyz)

	// Debug endpoints (only when enabled).
	if h.Debug {
		r.Route("/debug/pprof", func(r chi.Router) {
			r.HandleFunc("/", pprof.Index)
			r.HandleFunc("/cmdline", pprof.Cmdline)
			r.HandleFunc("/profile", pprof.Profile)
			r.HandleFunc("/symbol", pprof.Symbol)
			r.HandleFunc("/trace", pprof.Trace)
		})
	}

	// JSON API v1 routes.
	h.RegisterV1Routes(r)

	// Inspect page: server-rendered request list.
	r.Get("/e/{endpointID}", HandleInspect(h.Repo))

	// SSE stream for live updates.
	r.Get("/e/{endpointID}/events", HandleSSE(h.Repo))

	// Mock configuration.
	r.Put("/e/{endpointID}/mock", HandleMock(h.Repo))

	// Replay: one-click re-send of a captured request.
	r.Post("/e/{endpointID}/requests/{requestID}/replay", HandleReplay(h.Repo))

	// Capture webhook: any method on /{endpointID} (wildcard, must be last).
	r.HandleFunc("/{endpointID}", h.capture)
	r.HandleFunc("/{endpointID}/*", h.capture)
}

// capture handles incoming webhook captures on /{endpointID}.
func (h *Handler) capture(w http.ResponseWriter, r *http.Request) {
	eid := r.PathValue("endpointID")
	if eid == "" {
		writeError(w, http.StatusBadRequest, "missing endpoint ID")
		return
	}

	ctx := r.Context()

	// Auto-create endpoint if it doesn't exist.
	if _, err := h.Repo.GetEndpoint(ctx, eid); err != nil {
		h.Repo.CreateEndpoint(ctx, eid)
	}

	// Collect headers as JSON.
	hdr := map[string]string{}
	for k, v := range r.Header {
		hdr[k] = strings.Join(v, ", ")
	}
	hdrJSON, _ := json.Marshal(hdr)

	// Read body.
	var body []byte
	if r.Body != nil {
		body, _ = io.ReadAll(r.Body)
		r.Body.Close()
	}

	// Store the request.
	req := &store.Request{
		Method:  r.Method,
		Path:    r.URL.Path,
		Headers: string(hdrJSON),
		Query:   r.URL.RawQuery,
		Body:    body,
	}
	if err := h.Repo.AppendRequest(ctx, eid, req); err != nil {
		writeError(w, http.StatusInternalServerError, "failed to store request")
		return
	}

	// Broadcast via SSE.
	broadcastRequest(eid, req)

	// Look up mock response for this endpoint.
	mock, err := h.Repo.GetMock(ctx, eid)
	if err == nil && mock != nil {
		for k, v := range mock.Headers {
			w.Header().Set(k, v)
		}
		w.WriteHeader(mock.Status)
		if mock.Body != nil {
			w.Write(mock.Body)
		}
		return
	}

	// Default: return 200 OK with empty JSON.
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	json.NewEncoder(w).Encode(map[string]interface{}{"ok": true})
}

// Healthz returns 200 OK for liveness probes.
func Healthz(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok\n"))
}

// Readyz returns 200 OK if the service is ready to handle requests.
// It checks database connectivity.
func (h *Handler) Readyz(w http.ResponseWriter, r *http.Request) {
	if err := h.Repo.Ping(r.Context()); err != nil {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusServiceUnavailable)
		json.NewEncoder(w).Encode(map[string]string{"error": "database not ready"})
		return
	}
	w.Header().Set("Content-Type", "text/plain")
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("ok\n"))
}

// writeError writes a JSON error response.
func writeError(w http.ResponseWriter, status int, msg string) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(map[string]string{"error": msg})
}
