package httpx

import (
	"net/http"

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

	"github.com/yan1h/agent-coding-workflow/internal/transport/http/middleware"
)

// RouterDeps bundles the dependencies needed to assemble the router.
// MVP exposes only liveness/readiness probe hooks; later phases (auth
// resolver, module handlers, etc.) will add fields here rather than
// growing NewRouter's signature.
type RouterDeps struct {
	// HealthCheck is invoked from GET /healthz. Returning a non-nil
	// error renders 503 with the error message as the body. If nil,
	// /healthz unconditionally returns 200 "ok".
	HealthCheck func(*http.Request) error
	// ReadyCheck is invoked from GET /readyz with the same contract.
	ReadyCheck func(*http.Request) error
	// SPAHandler 为 nil 时禁用前端 fallback；非 nil 时挂到 chi NotFound，
	// 把未匹配的请求交给 SPA handler 处理（静态文件 + index.html 回退）。
	SPAHandler http.Handler
	// Logger 用于 Recover 和 Logger 中间件。为 nil 时跳过日志相关中间件。
	Logger *slog.Logger
	// Security 是安全响应头中间件配置（cfg-gated；Enabled=false 时透传）。
	Security middleware.SecurityHeadersConfig
	// CORS 是跨域中间件配置（cfg-gated；Enabled=false 时透传）。默认 off，
	// 避免误配破坏 SPA + WS 握手；需要时显式开启并配置 origin 白名单。
	CORS middleware.CORSConfig
	// MetricsHandler 为 nil 时不挂 /metrics；非 nil 时挂到 GET /metrics。
	// 暴露成本/会话数，必须经 MetricsAuthToken 门禁（见 NewRouter）。
	MetricsHandler http.Handler
	// MetricsAuthToken 非空时，/metrics 要求 Authorization: Bearer <token>
	// 或 ?token=<token> 匹配，否则 401。为空时 /metrics 不做令牌校验（仅
	// 适用于绑定内网 / 由反代鉴权的部署）。
	MetricsAuthToken string
}

// NewRouter builds the application's chi.Router with the standard
// liveness/readiness endpoints attached. The return type is the
// chi.Router interface (not *chi.Mux) so call sites depend only on
// behaviour shared with future routing layers.
func NewRouter(deps RouterDeps) chi.Router {
	r := chi.NewRouter()

	// 所有中间件必须在注册路由之前添加（chi 的强制要求）。
	r.Use(middleware.RequestID)
	// 安全头与 CORS 紧随 RequestID、在路由挂载之前；二者均 cfg-gated，
	// Enabled=false 时为透传，默认行为与历史一致。
	r.Use(middleware.SecurityHeaders(deps.Security))
	r.Use(middleware.CORS(deps.CORS))
	if deps.Logger != nil {
		r.Use(middleware.Recover(deps.Logger))
		r.Use(middleware.Logger(deps.Logger))
	}

	r.Get("/healthz", func(w http.ResponseWriter, req *http.Request) {
		if deps.HealthCheck != nil {
			if err := deps.HealthCheck(req); err != nil {
				http.Error(w, err.Error(), http.StatusServiceUnavailable)
				return
			}
		}
		_, _ = w.Write([]byte("ok"))
	})

	r.Get("/readyz", func(w http.ResponseWriter, req *http.Request) {
		if deps.ReadyCheck != nil {
			if err := deps.ReadyCheck(req); err != nil {
				http.Error(w, err.Error(), http.StatusServiceUnavailable)
				return
			}
		}
		_, _ = w.Write([]byte("ok"))
	})

	// /metrics（Prometheus）：admin-gated via static bearer token. 挂在 SPA
	// NotFound 之前，确保未匹配请求不会落到 SPA fallback。
	if deps.MetricsHandler != nil {
		token := deps.MetricsAuthToken
		mh := deps.MetricsHandler
		r.Get("/metrics", func(w http.ResponseWriter, req *http.Request) {
			if token != "" && !metricsTokenOK(req, token) {
				http.Error(w, "unauthorized", http.StatusUnauthorized)
				return
			}
			mh.ServeHTTP(w, req)
		})
	}

	if deps.SPAHandler != nil {
		r.NotFound(deps.SPAHandler.ServeHTTP)
	}

	return r
}

// metricsTokenOK 校验 /metrics 请求是否携带匹配的令牌（Authorization: Bearer
// <token> 或 ?token=<token>）。
func metricsTokenOK(req *http.Request, token string) bool {
	const prefix = "Bearer "
	if h := req.Header.Get("Authorization"); len(h) > len(prefix) && h[:len(prefix)] == prefix {
		if h[len(prefix):] == token {
			return true
		}
	}
	return req.URL.Query().Get("token") == token
}
