package geo

// HTTP handlers for the geo abstraction. Every response degrades gracefully:
// when a provider is not configured the payload is {available:false} with
// HTTP 200 — the ERP renders an explicit "unavailable" state instead of
// failing (map audit 2026-08-25).

import (
	"context"
	"net/http"
	"strconv"
	"strings"

	"github.com/TaruLab/keluargamart-backend/pkg/httpresponse"
	"github.com/TaruLab/keluargamart-backend/pkg/logger"
	"github.com/TaruLab/keluargamart-backend/pkg/middleware"
)

type Handler struct {
	router   Router
	geocoder Geocoder
}

func NewHandler(router Router, geocoder Geocoder) *Handler {
	return &Handler{router: router, geocoder: geocoder}
}

func pointFromQuery(r *http.Request, prefix string) (Point, bool) {
	q := r.URL.Query()
	lat, err1 := strconv.ParseFloat(q.Get(prefix+"_lat"), 64)
	lng, err2 := strconv.ParseFloat(q.Get(prefix+"_lng"), 64)
	if err1 != nil || err2 != nil {
		return Point{}, false
	}
	if lat < -90 || lat > 90 || lng < -180 || lng > 180 {
		return Point{}, false
	}
	return Point{Lat: lat, Lng: lng}, true
}

// Route serves GET /geo/route?from_lat&from_lng&to_lat&to_lng[&via1_lat&via1_lng...].
func (h *Handler) Route(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFrom(r.Context())
	from, ok := pointFromQuery(r, "from")
	to, ok2 := pointFromQuery(r, "to")
	if !ok || !ok2 || h.router == nil {
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": false,
			"reason": "routing provider not configured"})
		return
	}
	var via []Point
	for i := 1; i <= 5; i++ {
		if p, ok := pointFromQuery(r, "via"+strconv.Itoa(i)); ok {
			via = append(via, p)
		}
	}
	res, err := h.router.Route(r.Context(), from, to, via)
	if err != nil {
		// Provider failure must degrade gracefully.
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": false,
			"reason": strings.TrimSpace(errString(r.Context(), err))})
		return
	}
	httpresponse.OK(w, http.StatusOK, rid, res)
}

// Geocode serves GET /geo/geocode?q=...&lat=&lng= (forward OR reverse).
func (h *Handler) Geocode(w http.ResponseWriter, r *http.Request) {
	rid := middleware.RequestIDFrom(r.Context())
	if h.geocoder == nil {
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": false,
			"reason": "geocoder not configured (self-host Nominatim or wire a provider)"})
		return
	}
	if q := strings.TrimSpace(r.URL.Query().Get("q")); q != "" {
		hits, err := h.geocoder.Forward(r.Context(), q)
		if err != nil {
			httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": false,
				"reason": strings.TrimSpace(errString(r.Context(), err))})
			return
		}
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": true, "results": hits})
		return
	}
	if p, ok := pointFromQuery(r, "point"); ok {
		addr, err := h.geocoder.Reverse(r.Context(), p)
		if err != nil {
			httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": false,
				"reason": strings.TrimSpace(errString(r.Context(), err))})
			return
		}
		httpresponse.OK(w, http.StatusOK, rid, map[string]any{"available": true, "address": addr})
		return
	}
	httpresponse.Error(w, http.StatusBadRequest, rid, httpresponse.CodeValidation,
		"provide ?q= for forward geocoding or ?point_lat=&point_lng= for reverse", nil)
}

func errString(ctx context.Context, err error) string {
	if err == nil {
		return ""
	}
	// The provider's own text is not returned to the caller. It names the
	// upstream host it dialled and, for a routing provider, the coordinates and
	// via points, and this payload is served with HTTP 200 — so it is a body
	// the client reads, not an error channel. The detail goes to the log.
	logger.FromContext(ctx).Error("geo_provider_error", "err", err)
	return "provider unavailable"
}
