// gateway is the Lakehouse-Go HTTP ingress. D6 promotes the D1
// stub endpoints into real reverse-proxies fronting all four backing
// services (storaged, catalogd, ingestd, queryd) on a single bind.
//
// Routes:
//   /v1/storage/*  → storaged
//   /v1/catalog/*  → catalogd
//   /v1/ingest     → ingestd
//   /v1/sql        → queryd
//
// The /v1 prefix lives at the edge — internal services route on
// /storage, /catalog, /ingest, /sql. Per Kimi K2 finding from the
// D1 plan review: httputil.NewSingleHostReverseProxy preserves the
// inbound path by default, so the proxy helper strips /v1 in its
// Director before forwarding.
package main

import (
	"flag"
	"log/slog"
	"net/url"
	"os"

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

	"git.agentview.dev/profit/golangLAKEHOUSE/internal/gateway"
	"git.agentview.dev/profit/golangLAKEHOUSE/internal/shared"
)

func main() {
	configPath := flag.String("config", "lakehouse.toml", "path to TOML config")
	flag.Parse()

	cfg, err := shared.LoadConfig(*configPath)
	if err != nil {
		slog.Error("config", "err", err)
		os.Exit(1)
	}

	upstreams := map[string]string{
		"storaged_url": cfg.Gateway.StoragedURL,
		"catalogd_url": cfg.Gateway.CatalogdURL,
		"ingestd_url":  cfg.Gateway.IngestdURL,
		"queryd_url":   cfg.Gateway.QuerydURL,
		"vectord_url":  cfg.Gateway.VectordURL,
		"embedd_url":   cfg.Gateway.EmbeddURL,
		"pathwayd_url": cfg.Gateway.PathwaydURL,
		"matrixd_url":  cfg.Gateway.MatrixdURL,
		"observerd_url": cfg.Gateway.ObserverdURL,
		"chatd_url":    cfg.Gateway.ChatdURL,
		"validatord_url": cfg.Gateway.ValidatordURL,
	}
	for k, v := range upstreams {
		if v == "" {
			slog.Error("config", "err", "gateway."+k+" is required")
			os.Exit(1)
		}
	}

	// Per scrum O-WARN2 (Opus): url.Parse is permissive — a typo
	// like "127.0.0.1:3211" (missing scheme) parses without error
	// but produces empty Host, and every proxied request 502s. Fail
	// fast at startup if scheme/host are missing so misconfigs
	// surface in `systemctl status gateway` rather than at first traffic.
	storagedURL := mustParseUpstream("storaged_url", cfg.Gateway.StoragedURL)
	catalogdURL := mustParseUpstream("catalogd_url", cfg.Gateway.CatalogdURL)
	ingestdURL := mustParseUpstream("ingestd_url", cfg.Gateway.IngestdURL)
	querydURL := mustParseUpstream("queryd_url", cfg.Gateway.QuerydURL)
	vectordURL := mustParseUpstream("vectord_url", cfg.Gateway.VectordURL)
	embeddURL := mustParseUpstream("embedd_url", cfg.Gateway.EmbeddURL)
	pathwaydURL := mustParseUpstream("pathwayd_url", cfg.Gateway.PathwaydURL)
	matrixdURL := mustParseUpstream("matrixd_url", cfg.Gateway.MatrixdURL)
	observerdURL := mustParseUpstream("observerd_url", cfg.Gateway.ObserverdURL)
	chatdURL := mustParseUpstream("chatd_url", cfg.Gateway.ChatdURL)
	validatordURL := mustParseUpstream("validatord_url", cfg.Gateway.ValidatordURL)

	storagedProxy := gateway.NewProxyHandler(storagedURL)
	catalogdProxy := gateway.NewProxyHandler(catalogdURL)
	ingestdProxy := gateway.NewProxyHandler(ingestdURL)
	querydProxy := gateway.NewProxyHandler(querydURL)
	vectordProxy := gateway.NewProxyHandler(vectordURL)
	embeddProxy := gateway.NewProxyHandler(embeddURL)
	pathwaydProxy := gateway.NewProxyHandler(pathwaydURL)
	matrixdProxy := gateway.NewProxyHandler(matrixdURL)
	observerdProxy := gateway.NewProxyHandler(observerdURL)
	chatdProxy := gateway.NewProxyHandler(chatdURL)
	validatordProxy := gateway.NewProxyHandler(validatordURL)

	if err := shared.Run("gateway", cfg.Gateway.Bind, func(r chi.Router) {

		// Storage / catalog have multi-segment paths under their
		// prefix (e.g. /v1/storage/get/<key>). chi's `*` wildcard
		// captures the rest of the path.
		r.Handle("/v1/storage/*", storagedProxy)
		r.Handle("/v1/catalog/*", catalogdProxy)
		// Ingest + sql are single endpoints. We accept any method
		// (GET/POST/etc) and let the backing service decide. ingestd
		// only accepts POST; queryd only accepts POST. Other methods
		// will get the backend's 405.
		r.Handle("/v1/ingest", ingestdProxy)
		r.Handle("/v1/sql", querydProxy)
		// Vector search routes — /v1/vectors/index, /v1/vectors/index/{name}/...
		r.Handle("/v1/vectors/*", vectordProxy)
		// Embedding service — /v1/embed
		r.Handle("/v1/embed", embeddProxy)
		// Pathway memory — /v1/pathway/*
		r.Handle("/v1/pathway/*", pathwaydProxy)
		// Matrix indexer — /v1/matrix/*  (multi-corpus retrieve+merge per SPEC §3.4)
		r.Handle("/v1/matrix/*", matrixdProxy)
		// Observer — /v1/observer/*  (autonomous-iteration witness loop)
		r.Handle("/v1/observer/*", observerdProxy)
		// Chat — /v1/chat (LLM dispatcher) + /v1/chat/providers
		r.Handle("/v1/chat", chatdProxy)
		r.Handle("/v1/chat/*", chatdProxy)
		// Validator — /v1/validate (single-shot) + /v1/iterate (loop)
		r.Handle("/v1/validate", validatordProxy)
		r.Handle("/v1/iterate", validatordProxy)
	}, cfg.Auth); err != nil {
		slog.Error("server", "err", err)
		os.Exit(1)
	}
}

// mustParseUpstream parses an upstream URL string and validates that
// scheme + host are non-empty. Exits the process on failure — gateway
// can't function without a valid upstream so failing fast is the
// right call. Per scrum O-WARN2.
func mustParseUpstream(name, raw string) *url.URL {
	u, err := url.Parse(raw)
	if err != nil {
		slog.Error("config", "err", "parse "+name+": "+err.Error())
		os.Exit(1)
	}
	if u.Scheme == "" || u.Host == "" {
		slog.Error("config", "err", name+" must include scheme + host (got "+raw+")")
		os.Exit(1)
	}
	return u
}
