// Package api provides the REST API server for MCP Registry access.
package api

import (
	"encoding/json"
	"log/slog"
	"net/http"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
	"github.com/stacklok/toolhive/pkg/versions"
	"github.com/swaggo/swag/v2"

	// Import generated docs package to register OpenAPI spec via init()
	_ "github.com/stacklok/toolhive-registry-server/docs/thv-registry-api"
	v01 "github.com/stacklok/toolhive-registry-server/internal/api/registry/v01"
	apiv1 "github.com/stacklok/toolhive-registry-server/internal/api/v1"
	"github.com/stacklok/toolhive-registry-server/internal/auth"
	"github.com/stacklok/toolhive-registry-server/internal/config"
	"github.com/stacklok/toolhive-registry-server/internal/service"
)

// ServerOption configures the registry API server
type ServerOption func(*serverConfig)

// serverConfig holds the server configuration
type serverConfig struct {
	// These first two fields are private because they are not meant to be
	// shared with the other layers of the application.
	middlewares     []func(http.Handler) http.Handler
	authInfoHandler http.Handler
	authConfig      *config.AuthConfig
}

// WithMiddlewares adds middleware to the server
func WithMiddlewares(mw ...func(http.Handler) http.Handler) ServerOption {
	return func(cfg *serverConfig) {
		cfg.middlewares = append(cfg.middlewares, mw...)
	}
}

// WithAuthInfoHandler sets the auth info handler to be mounted at /.well-known/oauth-protected-resource
func WithAuthInfoHandler(handler http.Handler) ServerOption {
	return func(cfg *serverConfig) {
		cfg.authInfoHandler = handler
	}
}

// WithAuthConfig sets the authentication configuration used by the v1 router.
// Both role-based access control (Authz) and publish-time claim enforcement
// (Mode) are derived from it.
func WithAuthConfig(authCfg *config.AuthConfig) ServerOption {
	return func(cfg *serverConfig) {
		cfg.authConfig = authCfg
	}
}

// NewServer creates and configures the HTTP router with the given service and options
func NewServer(svc service.RegistryService, opts ...ServerOption) *chi.Mux {
	// Initialize configuration with defaults
	cfg := &serverConfig{
		middlewares: []func(http.Handler) http.Handler{},
	}

	// Apply options
	for _, opt := range opts {
		opt(cfg)
	}

	r := chi.NewRouter()

	// Apply middleware
	for _, mw := range cfg.middlewares {
		r.Use(mw)
	}

	// Mount OpenAPI endpoint
	r.Get("/openapi.json", openAPIHandler)

	// Mount auth info handler at well-known endpoint (if configured)
	if cfg.authInfoHandler != nil {
		r.Handle("/.well-known/oauth-protected-resource", cfg.authInfoHandler)
	}

	// Mount MCP Registry API v0.1 routes
	r.Mount("/registry", v01.Router(svc))
	r.Mount("/v1", apiv1.Router(svc, cfg.authConfig))

	return r
}

// NewInternalServer creates a minimal HTTP router for internal operational
// endpoints (health, readiness, version). These endpoints are intended to
// run on a separate port so that Kubernetes probes hit a dedicated server
// that carries no authentication or application middleware.
func NewInternalServer(svc service.RegistryService) *chi.Mux {
	r := chi.NewRouter()
	r.Get("/health", healthHandler)
	r.Get("/readiness", readinessHandler(svc))
	r.Get("/version", versionHandler)
	return r
}

// LoggingMiddleware logs HTTP requests, including the authenticated subject
// when available. It installs a mutable identity holder before the chain
// runs because Go's context is replaced (not mutated) by inner middleware
// — the auth middleware's r.WithContext(ctx) is invisible here without a
// shared holder. Auth populates the holder via auth.SetIdentity on success.
func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		ww := middleware.NewWrapResponseWriter(w, r.ProtoMajor)

		ctx := auth.WithIdentityHolder(r.Context())
		r = r.WithContext(ctx)

		next.ServeHTTP(ww, r)

		// Determine log level based on status code
		status := ww.Status()
		duration := time.Since(start)
		logLevel := slog.LevelInfo
		if status >= 500 {
			logLevel = slog.LevelError
		} else if status >= 400 {
			logLevel = slog.LevelWarn
		}

		sub, user := auth.IdentityFromContext(r.Context())
		if sub == "" {
			sub = auth.AnonymousSubject
		}

		slog.Log(r.Context(), logLevel, "HTTP request",
			"method", r.Method,
			"path", r.URL.Path,
			"status", status,
			"duration_ms", duration.Milliseconds(),
			"response_bytes", ww.BytesWritten(),
			"request_id", middleware.GetReqID(r.Context()),
			"remote_addr", r.RemoteAddr,
			"sub", sub,
			"user", user,
		)
	})
}

// openAPIHandler handles OpenAPI specification requests
//
// @Summary		OpenAPI specification
// @Description	Get the OpenAPI 3.1.0 specification for this API
// @Tags		system
// @Produce		json
// @Success		200	{object}	object	"OpenAPI 3.1.0 specification"
// @Failure		500	{object}	map[string]string
// @Router		/openapi.json [get]
func openAPIHandler(w http.ResponseWriter, _ *http.Request) {
	doc, err := swag.ReadDoc("swagger")
	if err != nil {
		slog.Error("Failed to read OpenAPI specification", "error", err)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusInternalServerError)
		errorResp := map[string]string{
			"error": "failed to read OpenAPI specification",
		}
		if encodeErr := json.NewEncoder(w).Encode(errorResp); encodeErr != nil {
			slog.Error("Failed to encode OpenAPI error response", "error", encodeErr)
		}
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	_, _ = w.Write([]byte(doc))
}

// healthHandler handles health check requests.
// Served on the internal port only — not part of the public API.
func healthHandler(w http.ResponseWriter, _ *http.Request) {
	response := HealthResponse{Status: "healthy"}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	if err := json.NewEncoder(w).Encode(response); err != nil {
		slog.Error("Failed to encode health response", "error", err)
	}
}

// readinessHandler handles readiness check requests.
// Served on the internal port only — not part of the public API.
func readinessHandler(svc service.RegistryService) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if err := svc.CheckReadiness(r.Context()); err != nil {
			slog.WarnContext(r.Context(), "Readiness check failed",
				"error", err,
				"remote_addr", r.RemoteAddr)
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusServiceUnavailable)
			errorResp := map[string]string{
				"error": "service not ready",
			}
			if encodeErr := json.NewEncoder(w).Encode(errorResp); encodeErr != nil {
				slog.Error("Failed to encode readiness error response", "error", encodeErr)
			}
			return
		}

		response := ReadinessResponse{Status: "ready"}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		if err := json.NewEncoder(w).Encode(response); err != nil {
			slog.Error("Failed to encode readiness response", "error", err)
		}
	}
}

// versionHandler handles version information requests.
// Served on the internal port only — not part of the public API.
func versionHandler(w http.ResponseWriter, _ *http.Request) {
	info := versions.GetVersionInfo()

	response := VersionResponse{
		Version:   info.Version,
		Commit:    info.Commit,
		BuildDate: info.BuildDate,
		GoVersion: info.GoVersion,
		Platform:  info.Platform,
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)

	if err := json.NewEncoder(w).Encode(response); err != nil {
		slog.Error("Failed to encode version info", "error", err)
	}
}
