package route

import (
	"fmt"
	"log/slog"
	"net/http"
	"runtime/debug"
	"time"

	"mkiso-server/internal/config"
	"mkiso-server/internal/handler"
	"mkiso-server/internal/middleware"
	"mkiso-server/internal/service"
)

// Setup creates the HTTP handler with all routes wired.
// Stage 1: no auth.
func Setup(cfg *config.Config, isoSvc *service.ISOService, relaySvc *service.RelayService) http.Handler {
	mux := http.NewServeMux()

	// Health check — always public
	mux.HandleFunc("GET /api/health", handler.Health(cfg))

	// ISO download endpoints
	mux.HandleFunc("GET /api/iso/download", handler.DownloadSingle(isoSvc))
	mux.HandleFunc("GET /api/iso/download-multiple", handler.DownloadMultiple(isoSvc))

	// Print/relay endpoint (single endpoint handles both single and multi via comma detection)
	mux.HandleFunc("GET /api/iso/print", handler.PrintISO(relaySvc))

	// Wrap with middleware chain: Recovery → RequestID → Logging
	return middleware.Chain(
		mux,
		recoveryMiddleware,
		requestIDMiddleware,
		loggingMiddleware,
	)
}

// SetupSecure creates the HTTP handler with API key authentication on /api/iso/*.
// Stage 2: auth enabled.
func SetupSecure(cfg *config.Config, isoSvc *service.ISOService, relaySvc *service.RelayService) http.Handler {
	mux := http.NewServeMux()

	// Health check — always public
	mux.HandleFunc("GET /api/health", handler.Health(cfg))

	// Protected routes
	apiKeyMw := middleware.APIKey(cfg.Auth.APIKey)
	mux.Handle("GET /api/iso/download", apiKeyMw(http.HandlerFunc(handler.DownloadSingle(isoSvc))))
	mux.Handle("GET /api/iso/download-multiple", apiKeyMw(http.HandlerFunc(handler.DownloadMultiple(isoSvc))))
	mux.Handle("GET /api/iso/print", apiKeyMw(http.HandlerFunc(handler.PrintISO(relaySvc))))

	return middleware.Chain(
		mux,
		recoveryMiddleware,
		requestIDMiddleware,
		loggingMiddleware,
	)
}

// recoveryMiddleware catches panics, logs the stack trace, and returns 500.
func recoveryMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rec := recover(); rec != nil {
				slog.Error("handler panic",
					"method", r.Method,
					"path", r.URL.Path,
					"panic", rec,
					"stack", string(debug.Stack()),
				)
				http.Error(w, `{"error":"internal server error"}`, http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

// requestIDMiddleware adds a unique request ID to each request context.
func requestIDMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rid := r.Header.Get("X-Request-ID")
		if rid == "" {
			rid = generateRequestID()
		}
		w.Header().Set("X-Request-ID", rid)
		next.ServeHTTP(w, r)
	})
}

// loggingMiddleware logs each request with method, path, status, and duration.
func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
		next.ServeHTTP(lrw, r)
		slog.Info("request",
			"method", r.Method,
			"path", r.URL.Path,
			"query", r.URL.RawQuery,
			"status", lrw.statusCode,
			"duration_ms", time.Since(start).Milliseconds(),
		)
	})
}

// loggingResponseWriter wraps http.ResponseWriter to capture the status code.
type loggingResponseWriter struct {
	http.ResponseWriter
	statusCode int
}

func (lrw *loggingResponseWriter) WriteHeader(code int) {
	lrw.statusCode = code
	lrw.ResponseWriter.WriteHeader(code)
}

// generateRequestID returns a simple unique request ID.
func generateRequestID() string {
	return fmt.Sprintf("req-%d", time.Now().UnixNano())
}
