package main

import (
	"bookhoard/internal/app"
	"bookhoard/internal/config"
	"bookhoard/internal/database"
	"bookhoard/internal/handlers"
	"bookhoard/internal/middleware"
	ratelimit "bookhoard/internal/middleware"
	"bookhoard/internal/router"
	"bookhoard/internal/services"
	"bookhoard/internal/sync"
	"context"
	"log"
	"time"

	_ "time/tzdata"

	"github.com/go-playground/validator/v10"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/labstack/echo/v5"
	echomiddleware "github.com/labstack/echo/v5/middleware"
)

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

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

func main() {
	cfg := config.LoadConfig()

	dbPool, err := pgxpool.New(context.Background(), cfg.DatabaseURL())
	if err != nil {
		log.Fatal("Failed to connect to database:", err)
	}
	defer dbPool.Close()

	queries := database.New(dbPool)

	// Initialize database schema
	log.Println("🔧 Ensuring database schema is initialized...")
	ctx := context.Background()
	if err := database.Initialize(ctx, dbPool); err != nil {
		log.Fatal("❌ Database schema initialization failed:", err)
	}
	log.Println("✅ Database schema initialized and verified, starting server...")

	// Load tunable settings from the DB into the registry. All values fall back
	// to compiled defaults if a row is missing, so this never blocks startup.
	registry := database.NewSettingsRegistry(queries)
	if err := registry.Load(ctx); err != nil {
		log.Printf("⚠️ Could not load system settings (using defaults): %v", err)
	}
	// Wire the registry into the package-level password validator so live
	// rule changes apply to the echo struct-tag validator and ValidatePassword.
	middleware.SetDefaultPasswordSettings(registry)

	// Seed base_url from env var if not already configured. Uses conditional
	// UPDATE so admin-set values are never overwritten on restart.
	if cfg.BaseURL != "" {
		_, err = dbPool.Exec(ctx, `
			INSERT INTO system_config (key, value)
			VALUES ('base_url', $1)
			ON CONFLICT (key) DO UPDATE
			SET value = EXCLUDED.value
			WHERE system_config.value = ''
		`, cfg.BaseURL)
		if err != nil {
			log.Printf("⚠️ Could not seed base_url: %v", err)
		} else {
			// Also seed derived URLs
			for key, suffix := range map[string]string{
				"opds_base_url": "/opds",
				"api_base_url":  "/api",
			} {
				_, _ = dbPool.Exec(ctx, `
					INSERT INTO system_config (key, value)
					VALUES ($1, $2)
					ON CONFLICT (key) DO UPDATE
					SET value = EXCLUDED.value
					WHERE system_config.value = ''
				`, key, cfg.BaseURL+suffix)
			}
		}
	}

	// Create login attempt tracker from configured (or default) lockout policy.
	loginMaxAttempts, loginLockout := registry.LoginLockout()
	loginAttemptTracker := ratelimit.NewLoginAttemptTracker(loginMaxAttempts, loginLockout, 5*time.Minute)

	authHandler := handlers.NewAuthHandler(queries, cfg.JWTSecret, loginAttemptTracker)
	authHandler.SetSettings(registry)
	systemSettingsHandler := handlers.NewSystemSettingsHandler(queries)
	systemSettingsHandler.SetSettings(registry)
	sidecarHandler := handlers.NewSidecarHandler(queries, cfg)
	sidecarHandler.SetSettings(registry)
	libraryHandler := handlers.NewLibraryHandler(queries)
	deviceHandler := handlers.NewDeviceHandler(queries, cfg.JWTSecret, cfg)
	deviceAuthMiddleware := middleware.NewDeviceAuthMiddleware(queries)
	deviceAuthMiddleware.SetSettings(registry)
	processingIssuesHandler := handlers.NewProcessingIssuesHandler(queries)
	hashConflictsHandler := handlers.NewHashConflictsHandler(queries)

	// Create WebSocket connection manager
	connManager := sync.NewConnectionManager()

	progressService := sync.NewProgressService(queries, connManager)
	annotationService := sync.NewAnnotationService(queries, connManager)
	annotationService.SetSettings(registry)
	maintenanceCancel := annotationService.StartDailyMaintenance()
	defer maintenanceCancel()

	queueProcessor := sync.NewSyncQueueProcessorWithConfig(queries, registry.SyncQueueConfig().Interval, registry.SyncQueueConfig().BatchSize)
	queueProcessor.SetProgressService(progressService)
	queueProcessor.SetAnnotationService(annotationService)

	// Create library service
	libraryService := services.NewLibraryService(queries)

	// Sync Go AllowedExtensions into DB so API clients see correct extensions
	libraryService.SyncAllowedExtensions(context.Background())

	// Create worker for background tasks
	workerCfg := registry.WorkerPoolConfig()
	worker := services.NewWorkerWithConfig(workerCfg.Size, workerCfg.QueueCap, connManager)
	services.WorkerInstance = worker

	koreaderHandler := handlers.NewKOReaderHandler(queries, connManager, queueProcessor)
	koreaderHandler.SetProgressService(progressService)
	koreaderHandler.SetAnnotationService(annotationService)
	koreaderHandler.SetLibraryService(libraryService)
	wsHandler := handlers.NewWSHandler(queries, connManager, cfg.JWTSecret, deviceAuthMiddleware)
	conflictHandler := handlers.NewConflictHandler(queries, connManager)
	analyticsHandler := handlers.NewAnalyticsHandler(queries)
	queueHandler := handlers.NewQueueHandler(queries, queueProcessor)

	conversionService := services.NewConversionService(queries, "/var/bookhoard/cache/kepub")
	conversionService.SetSettings(registry)
	opdsHandler := handlers.NewOPDSHandler(queries, libraryService, conversionService)
	opdsHandler.SetSettings(registry)

	collectionHandler := handlers.NewCollectionHandler(queries, libraryService, connManager)
	dashboardService := services.NewDashboardService(queries)
	dashboardHandler := handlers.NewDashboardHandler(queries)
	seriesHandler := handlers.NewSeriesHandler(queries)
	filtersHandler := handlers.NewFiltersHandler(queries)
	mediaHandler := handlers.NewMediaHandler(queries, libraryService, worker)
	mediaHandler.SetProgressService(progressService)
	mediaHandler.SetAnnotationService(annotationService)
	matchingHandler := handlers.NewMatchingHandler(queries, connManager)
	jobsHandler := handlers.NewJobsHandler(queries, worker)

	e := echo.New()

	// Set up validator
	v := validator.New()

	// Register custom password complexity validator
	if err := ratelimit.RegisterPasswordValidation(v); err != nil {
		log.Fatal("Failed to register password validator:", err)
	}

	e.Validator = &CustomValidator{validator: v}

	// Middleware
	e.Use(echomiddleware.RequestLogger())
	e.Use(echomiddleware.Recover())
	e.Use(echomiddleware.CORSWithConfig(echomiddleware.CORSConfig{
		AllowOrigins:     []string{"*"},
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: false,
	}))
	e.Use(ratelimit.RequestTracingMiddleware(cfg))

	// Rate limiter for auth endpoints
	// rateLimiterConfig := ratelimit.RateLimiterConfig{
	// 	Enabled:           cfg.RateLimitEnabled,
	// 	RequestsPerMinute: cfg.RequestsPerMinute,
	// 	CleanupInterval:   5 * time.Minute,
	// }
	// rateLimiter := ratelimit.NewRateLimiter(rateLimiterConfig)
	// rateLimitMiddleware := ratelimit.RateLimiterMiddleware(rateLimiter) // Now in router/auth.go

	// ========================================================================
	// ROUTER REGISTRATION - Migrate routes to internal/router/ package
	// ========================================================================
	routerConfig := &router.Config{
		Echo:                    e,
		Queries:                 queries,
		Cfg:                     cfg,
		Settings:                registry,
		DBPool:                  dbPool,
		AuthHandler:             authHandler,
		LibraryHandler:          libraryHandler,
		DeviceHandler:           deviceHandler,
		MediaHandler:            mediaHandler,
		MatchingHandler:         matchingHandler,
		ProcessingIssuesHandler: processingIssuesHandler,
		HashConflictsHandler:    hashConflictsHandler,
		KOReaderHandler:         koreaderHandler,
		WSHandler:               wsHandler,
		ConflictHandler:         conflictHandler,
		AnalyticsHandler:        analyticsHandler,
		QueueHandler:            queueHandler,
		CollectionHandler:       collectionHandler,
		FiltersHandler:          filtersHandler,
		DashboardHandler:        dashboardHandler,
		DashboardService:        dashboardService,
		SeriesHandler:           seriesHandler,
		OPDSHandler:             opdsHandler,
		Worker:                  worker,
		SystemSettingsHandler:   systemSettingsHandler,
		SidecarHandler:          sidecarHandler,
		ConnManager:             connManager,
		QueueProcessor:          queueProcessor,
		ProgressService:         progressService,
		AnnotationService:       annotationService,
		DeviceAuthMiddleware:    deviceAuthMiddleware,
		JobsHandler:             jobsHandler,
		LoginTracker:            loginAttemptTracker,
		LibraryService:          libraryService,
	}

	// Register all routes and get ebook handler
	_ = router.RegisterRoutes(routerConfig)

	// ========================================================================
	// APPLICATION LIFECYCLE MANAGEMENT
	// ========================================================================

	// Create app with lifecycle management
	application := app.New(e)

	// ========================================================================
	// START SERVER (managed by app lifecycle)
	// ========================================================================
	log.Printf("Starting server on port %s", cfg.ServerPort)

	// Start HTTP server
	if err := application.StartServer(":" + cfg.ServerPort); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}

	// Start application lifecycle (blocks until shutdown signal)
	if err := application.Start(); err != nil {
		log.Fatalf("Application error: %v", err)
	}
}
