package router

import (
	"bookhoard/internal/config"
	"bookhoard/internal/database"
	"bookhoard/internal/handlers"
	"bookhoard/internal/middleware"
	ratelimit "bookhoard/internal/middleware"
	"bookhoard/internal/services"
	"bookhoard/internal/sync"
	"bookhoard/templates"
	"bytes"
	"context"
	"log"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/go-playground/validator/v10"
	"github.com/golang-jwt/jwt/v5"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgtype"
	echojwt "github.com/labstack/echo-jwt/v5"
	"github.com/labstack/echo/v5"
)

// CustomValidator wraps the go-playground validator
type CustomValidator struct {
	validator *validator.Validate
}

func (cv *CustomValidator) Validate(i interface{}) error {
	return cv.validator.Struct(i)
}

// Config holds all dependencies needed for route registration
type Config struct {
	Echo                    *echo.Echo
	Queries                 *database.Queries
	Cfg                     *config.Config
	Settings                *database.SettingsRegistry
	DBPool                  interface{} // pgxpool.Pool interface
	AuthHandler             *handlers.AuthHandler
	LibraryHandler          *handlers.LibraryHandler
	DeviceHandler           *handlers.DeviceHandler
	MediaHandler            *handlers.MediaHandler
	MatchingHandler         *handlers.MatchingHandler
	ProcessingIssuesHandler *handlers.ProcessingIssuesHandler
	HashConflictsHandler    *handlers.HashConflictsHandler
	KOReaderHandler         *handlers.KOReaderHandler
	WSHandler               *handlers.WSHandler
	ConflictHandler         *handlers.ConflictHandler
	AnalyticsHandler        *handlers.AnalyticsHandler
	QueueHandler            *handlers.QueueHandler
	CollectionHandler       *handlers.CollectionHandler
	Worker                  *services.Worker
	FiltersHandler          *handlers.FiltersHandler
	DashboardHandler        *handlers.DashboardHandler
	DashboardService        *services.DashboardService
	SeriesHandler           *handlers.SeriesHandler
	OPDSHandler             *handlers.OPDSHandler
	SystemSettingsHandler   *handlers.SystemSettingsHandler
	ConnManager             *sync.ConnectionManager
	QueueProcessor          *sync.SyncQueueProcessor
	ProgressService         *sync.ProgressService
	AnnotationService       *sync.AnnotationService
	DeviceAuthMiddleware    *middleware.DeviceAuthMiddleware
	LoginTracker            *ratelimit.LoginAttemptTracker
	ScannerHandler          *handlers.Handler
	JobsHandler             *handlers.JobsHandler
	SidecarHandler           *handlers.SidecarHandler
	ReaderHandler           *handlers.ReaderHandler
	LibraryService          *services.LibraryService
}

// getBaseURL returns the configured base URL from the database, falling back to
// the env var / config default. Uses a closure to adapt the database query to
// config.SystemConfigGetter.
func (cfg *Config) getBaseURL(ctx context.Context) string {
	getter := func(ctx context.Context, key string) (string, error) {
		row, err := cfg.Queries.GetSystemConfig(ctx, key)
		if err != nil {
			return "", err
		}
		return row.Value, nil
	}
	baseURL := config.GetBaseURL(ctx, getter)
	if baseURL == "" {
		baseURL = cfg.Cfg.BaseURL
	}
	return baseURL
}

// createJWTMiddleware creates a JWT middleware with proper user context setup
func createJWTMiddleware(cfg *Config) echo.MiddlewareFunc {
	return echojwt.WithConfig(echojwt.Config{
		SigningKey:  []byte(cfg.Cfg.JWTSecret),
		ContextKey:  "user",
		TokenLookup: "cookie:token,header:Authorization:Bearer ",
		SuccessHandler: func(c *echo.Context) error {
			token := c.Get("user").(*jwt.Token)
			claims := token.Claims.(jwt.MapClaims)
			c.Set("user_id", claims["user_id"])
			c.Set("user_role", claims["user_role"])
			c.Set("user_email", claims["user_email"])
			c.Set("user_username", claims["user_username"])

			// Parse UUID from string claims
			userIDStr, _ := claims["user_id"].(string)
			userUUID, err := uuid.Parse(userIDStr)
			if err != nil {
				return echo.NewHTTPError(http.StatusBadRequest, "invalid user ID in token")
			}

			c.Set("user", database.Users{
				ID:       pgtype.UUID{Bytes: userUUID, Valid: true},
				Email:    claims["user_email"].(string),
				Username: claims["user_username"].(string),
				Role:     claims["user_role"].(string),
			})
			return nil
		},
		ErrorHandler: func(c *echo.Context, err error) error {
			// Check if this is a page request (browser navigation)
			if wantsHTML(c.Request().Header) {
				// Page request → Redirect to login with message
				loginURL := "/login?session=expired"
				return c.Redirect(http.StatusFound, loginURL)
			}

			// API request → Return JSON error
			return c.JSON(http.StatusUnauthorized, map[string]string{
				"error":   "session_expired",
				"message": "Your session has expired. Please log in again.",
			})
		},
	})
}

// ensureUserExistsMiddleware checks if the authenticated user still exists in the database
func ensureUserExistsMiddleware(cfg *Config) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c *echo.Context) error {
			userIDStr, ok := c.Get("user_id").(string)
			if !ok {
				return next(c)
			}

			userUUID, err := uuid.Parse(userIDStr)
			if err != nil {
				log.Printf("Invalid UUID in user existence check: %v", err)
				return next(c)
			}

			// Check if user exists in database
			_, err = cfg.Queries.GetUser(c.Request().Context(), uuidToPGType(userUUID))
			if err != nil {
				log.Printf("User not found in database: %s", userIDStr)

				// Clear invalid cookie
				c.SetCookie(&http.Cookie{
					Name:     "token",
					Value:    "",
					Expires:  time.Now().Add(-24 * time.Hour),
					Path:     "/",
					HttpOnly: true,
					SameSite: http.SameSiteLaxMode,
				})

				// Redirect to login with session=invalid
				return c.Redirect(http.StatusFound, "/login?session=invalid")
			}

			return next(c)
		}
	}
}

// wantsHTML determines if the request expects HTML response
func wantsHTML(header http.Header) bool {
	// Check Accept header
	accept := header.Get("Accept")
	if accept != "" && (accept == "text/html" || strings.Contains(accept, "text/html")) {
		return true
	}

	// Check HTMX request
	if header.Get("HX-Request") == "true" {
		return true
	}

	// Check for AJAX requests (should get JSON)
	if header.Get("X-Requested-With") == "XMLHttpRequest" {
		return false
	}

	// Default to JSON for API routes
	return false
}

// RegisterRoutes registers all application routes
func RegisterRoutes(cfg *Config) *handlers.Handler {
	e := cfg.Echo

	// Set up validator
	v := validator.New()
	if err := ratelimit.RegisterPasswordValidation(v); err != nil {
		log.Fatal("Failed to register password validator:", err)
	}
	e.Validator = &CustomValidator{validator: v}

	// Setup redirect middleware - must run before all routes
	e.Pre(setupRedirectMiddleware(cfg))

	// Rate limiter. The per-minute value comes from the settings registry (DB);
	// the enabled flag stays env-driven since disabling rate limiting is a
	// deployment-time decision, not a runtime tunable.
	rateLimiterConfig := ratelimit.RateLimiterConfig{
		Enabled:           cfg.Cfg.RateLimitEnabled,
		RequestsPerMinute: cfg.Settings.AuthRateLimit(),
		CleanupInterval:   5 * time.Minute,
	}
	rateLimiter := ratelimit.NewRateLimiter(rateLimiterConfig)
	rateLimitMiddleware := ratelimit.RateLimiterMiddleware(rateLimiter)

	// Register core application routes (collections, devices, media, etc.) - ONCE
	jwtMiddleware := createJWTMiddleware(cfg)
	protected := e.Group("/api", jwtMiddleware)

	// Create scanner handler for scanner routes and progress routes
	scannerHandler := handlers.SetupRoutes(protected, cfg.Queries, cfg.ConnManager, cfg.QueueProcessor, cfg.Cfg)
	cfg.ScannerHandler = scannerHandler

	// Register route groups
	registerSetupRoutes(cfg)
	registerAuthRoutes(cfg, rateLimitMiddleware)
	registerLibraryRoutes(cfg)
	registerDeviceRoutes(cfg)
	registerSystemRoutes(cfg)
	registerSyncRoutes(cfg)
	registerCollectionsRoutes(cfg)
	registerSeriesRoutes(cfg)
	registerDashboardRoutes(cfg)
	registerMediaRoutes(cfg)
	registerSearchRoutes(cfg)
	registerMatchingRoutes(cfg)
	registerConflictRoutes(cfg)
	registerAnalyticsRoutes(cfg)
	registerQueueRoutes(cfg)
	registerJobRoutes(cfg)
	registerFiltersRoutes(cfg)
	registerOPDSRoutes(cfg)
	registerReaderRoutes(cfg)
	registerWebSocketRoutes(cfg)
	registerFrontendRoutes(cfg)
	registerDocumentationRoutes(cfg)
	e.Static("/static", "web/static")

	// Catch-all 404 handler - must be last
	e.GET("/*", func(c *echo.Context) error {
		var buf bytes.Buffer
		err := templates.ErrorPage("Page not found", "404").Render(c.Request().Context(), &buf)
		if err != nil {
			return err
		}
		return c.HTML(http.StatusNotFound, buf.String())
	})

	// Start background tasks (queue processor and connection cleanup)
	scannerHandler.StartBackgroundTasks()

	// Start watch mode for all libraries (after 2 second delay for DB)
	go func() {
		time.Sleep(2 * time.Second)
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		setting, err := cfg.Queries.GetSystemSetting(ctx, "auto_scan_enabled")
		enabled := true // default
		if err == nil && setting != "" {
			enabled, _ = strconv.ParseBool(setting)
		}

		if !enabled {
			log.Println("Auto-scan disabled in settings, skipping watch mode startup")
			return
		}

		log.Println("Starting watch mode for all libraries...")
		if err := scannerHandler.StartWatchModeForAllLibraries(context.Background()); err != nil {
			log.Printf("Failed to start watch mode: %v", err)
		} else {
			log.Println("Watch mode started successfully")
		}
	}()

	// One-time hash backfill: compute and store SHA-256 for media items
	// imported before hashing existed, then flag any content-duplicate groups
	// for admin review on the Hash Conflicts page. Runs independently of
	// auto-scan (it is a one-shot self-heal, not a recurring scan) and is a
	// no-op once every item is hashed. Delayed so it does not compete with
	// startup scans for disk I/O.
	go func() {
		time.Sleep(30 * time.Second)
		services.NewHashBackfillService(cfg.Queries).Run(context.Background())
	}()

	// Register progress routes with actual handler
	registerProgressRoutes(cfg, scannerHandler)

	// Register scanner routes (admin only)
	admin := protected.Group("", handlers.AdminMiddleware)
	registerScannerRoutes(admin, scannerHandler)

	// Hash conflict routes (admin only)
	admin.GET("/api/admin/hash-conflicts", cfg.HashConflictsHandler.ListHashConflicts)
	admin.POST("/api/admin/hash-conflicts/:id/resolve", cfg.HashConflictsHandler.ResolveHashConflict)

	return scannerHandler
}
