// Package gateway holds the reverse-proxy glue that fronts all four
// backing services on a single bind. The /v1 prefix lives at the
// edge — internal services route on `/storage`, `/catalog`, `/ingest`,
// `/sql`. Per D1-plan Kimi K2 finding: httputil.NewSingleHostReverseProxy
// preserves the inbound path by default, so the Director must strip
// `/v1` before forwarding or the backend gets a 404.
package gateway

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
)

// V1Prefix is the public API version namespace. Stripped before
// requests are forwarded to backing services.
const V1Prefix = "/v1"

// NewProxyHandler builds an http.Handler that reverse-proxies every
// request to upstream, with the V1Prefix stripped from req.URL.Path
// (and RawPath, if present). Query string is preserved. Host header
// is rewritten so the backing service's chi router sees its expected
// host string, not the gateway's.
//
// The returned handler is intentionally minimal — connection pooling
// (via http.DefaultTransport), error responses (502 on upstream
// unreachable), and request logging (via shared.Run middleware) are
// all inherited from sane defaults.
func NewProxyHandler(upstream *url.URL) http.Handler {
	p := httputil.NewSingleHostReverseProxy(upstream)
	origDirector := p.Director
	p.Director = func(req *http.Request) {
		// Per scrum O-BLOCK (Opus): strip /v1 BEFORE origDirector
		// runs. The default Director joins target.Path + req.URL.Path
		// via singleJoiningSlash, so an upstream like
		// "http://host/api" produces "/api/v1/storage/..." after the
		// join — then TrimPrefix("/v1") is a no-op because the string
		// starts with "/api". Stripping first means the join sees the
		// already-clean path and produces "/api/storage/...".
		req.URL.Path = strings.TrimPrefix(req.URL.Path, V1Prefix)
		if req.URL.RawPath != "" {
			req.URL.RawPath = strings.TrimPrefix(req.URL.RawPath, V1Prefix)
		}
		origDirector(req)
		req.Host = upstream.Host
	}
	return p
}
