// Package routes defines the API route registration and wiring.
package routes

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/gin-contrib/gzip"
	"github.com/gin-gonic/gin"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"gorm.io/gorm"

	"github.com/Wikid82/charon/backend/internal/api/handlers"
	"github.com/Wikid82/charon/backend/internal/api/middleware"
	"github.com/Wikid82/charon/backend/internal/caddy"
	"github.com/Wikid82/charon/backend/internal/cerberus"
	"github.com/Wikid82/charon/backend/internal/config"
	"github.com/Wikid82/charon/backend/internal/logger"
	"github.com/Wikid82/charon/backend/internal/metrics"
	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
)

// Register wires up API routes and performs automatic migrations.
func Register(router *gin.Engine, db *gorm.DB, cfg config.Config) error {
	// Enable gzip compression for API responses (reduces payload size ~70%)
	router.Use(gzip.Gzip(gzip.DefaultCompression))

	// Apply security headers middleware globally
	// This sets CSP, HSTS, X-Frame-Options, etc.
	securityHeadersCfg := middleware.SecurityHeadersConfig{
		IsDevelopment: cfg.Environment == "development",
	}
	router.Use(middleware.SecurityHeaders(securityHeadersCfg))

	// AutoMigrate all models for Issue #5 persistence layer
	if err := db.AutoMigrate(
		&models.ProxyHost{},
		&models.Location{},
		&models.CaddyConfig{},
		&models.RemoteServer{},
		&models.SSLCertificate{},
		&models.AccessList{},
		&models.User{},
		&models.Setting{},
		&models.ImportSession{},
		&models.Notification{},
		&models.NotificationProvider{},
		&models.NotificationTemplate{},
		&models.NotificationConfig{},
		&models.UptimeMonitor{},
		&models.UptimeHeartbeat{},
		&models.UptimeHost{},
		&models.UptimeNotificationEvent{},
		&models.Domain{},
		&models.SecurityConfig{},
		&models.SecurityDecision{},
		&models.SecurityAudit{},
		&models.SecurityRuleSet{},
		&models.UserPermittedHost{}, // Join table for user permissions
		&models.CrowdsecPresetEvent{},
		&models.CrowdsecConsoleEnrollment{},
	); err != nil {
		return fmt.Errorf("auto migrate: %w", err)
	}

	// Clean up invalid Let's Encrypt certificate associations
	// Let's Encrypt certs are auto-managed by Caddy and should not be assigned via certificate_id
	logger.Log().Info("Cleaning up invalid Let's Encrypt certificate associations...")
	var hostsWithInvalidCerts []models.ProxyHost
	if err := db.Joins("LEFT JOIN ssl_certificates ON proxy_hosts.certificate_id = ssl_certificates.id").
		Where("ssl_certificates.provider = ?", "letsencrypt").
		Find(&hostsWithInvalidCerts).Error; err == nil {
		if len(hostsWithInvalidCerts) > 0 {
			for _, host := range hostsWithInvalidCerts {
				logger.Log().WithField("domain", host.DomainNames).Info("Removing invalid Let's Encrypt cert assignment")
				db.Model(&host).Update("certificate_id", nil)
			}
		}
	}

	router.GET("/api/v1/health", handlers.HealthHandler)

	// Metrics endpoint (Prometheus)
	reg := prometheus.NewRegistry()
	metrics.Register(reg)
	router.GET("/metrics", func(c *gin.Context) {
		promhttp.HandlerFor(reg, promhttp.HandlerOpts{}).ServeHTTP(c.Writer, c.Request)
	})

	api := router.Group("/api/v1")

	// Cerberus middleware applies the optional security suite checks (WAF, ACL, CrowdSec)
	cerb := cerberus.New(cfg.Security, db)
	api.Use(cerb.Middleware())

	// Caddy Manager declaration so it can be used across the entire Register function
	var caddyManager *caddy.Manager

	// Auth routes
	authService := services.NewAuthService(db, cfg)
	authHandler := handlers.NewAuthHandlerWithDB(authService, db)
	authMiddleware := middleware.AuthMiddleware(authService)

	// Backup routes
	backupService := services.NewBackupService(&cfg)
	backupHandler := handlers.NewBackupHandler(backupService)

	// Log routes
	logService := services.NewLogService(&cfg)
	logsHandler := handlers.NewLogsHandler(logService)

	// Notification Service (needed for multiple handlers)
	notificationService := services.NewNotificationService(db)

	// Remote Server Service (needed for Docker handler)
	remoteServerService := services.NewRemoteServerService(db)

	api.POST("/auth/login", authHandler.Login)
	api.POST("/auth/register", authHandler.Register)

	// Forward auth endpoint for Caddy (public, validates session internally)
	api.GET("/auth/verify", authHandler.Verify)
	api.GET("/auth/status", authHandler.VerifyStatus)

	// User handler (public endpoints)
	userHandler := handlers.NewUserHandler(db)
	api.GET("/setup", userHandler.GetSetupStatus)
	api.POST("/setup", userHandler.Setup)
	api.GET("/invite/validate", userHandler.ValidateInvite)
	api.POST("/invite/accept", userHandler.AcceptInvite)

	// Uptime Service - define early so it can be used during route registration
	uptimeService := services.NewUptimeService(db, notificationService)

	protected := api.Group("/")
	protected.Use(authMiddleware)
	{
		protected.POST("/auth/logout", authHandler.Logout)
		protected.GET("/auth/me", authHandler.Me)
		protected.POST("/auth/change-password", authHandler.ChangePassword)

		// Backups
		protected.GET("/backups", backupHandler.List)
		protected.POST("/backups", backupHandler.Create)
		protected.DELETE("/backups/:filename", backupHandler.Delete)
		protected.GET("/backups/:filename/download", backupHandler.Download)
		protected.POST("/backups/:filename/restore", backupHandler.Restore)

		// Logs
		protected.GET("/logs", logsHandler.List)
		protected.GET("/logs/:filename", logsHandler.Read)
		protected.GET("/logs/:filename/download", logsHandler.Download)
		protected.GET("/logs/live", handlers.LogsWebSocketHandler)

		// Security Notification Settings
		securityNotificationService := services.NewSecurityNotificationService(db)
		securityNotificationHandler := handlers.NewSecurityNotificationHandler(securityNotificationService)
		protected.GET("/security/notifications/settings", securityNotificationHandler.GetSettings)
		protected.PUT("/security/notifications/settings", securityNotificationHandler.UpdateSettings)

		// Settings
		settingsHandler := handlers.NewSettingsHandler(db)
		protected.GET("/settings", settingsHandler.GetSettings)
		protected.POST("/settings", settingsHandler.UpdateSetting)

		// SMTP Configuration
		protected.GET("/settings/smtp", settingsHandler.GetSMTPConfig)
		protected.POST("/settings/smtp", settingsHandler.UpdateSMTPConfig)
		protected.POST("/settings/smtp/test", settingsHandler.TestSMTPConfig)
		protected.POST("/settings/smtp/test-email", settingsHandler.SendTestEmail)

		// Auth related protected routes
		protected.GET("/auth/accessible-hosts", authHandler.GetAccessibleHosts)
		protected.GET("/auth/check-host/:hostId", authHandler.CheckHostAccess)

		// Feature flags (DB-backed with env fallback)
		featureFlagsHandler := handlers.NewFeatureFlagsHandler(db)
		protected.GET("/feature-flags", featureFlagsHandler.GetFlags)
		protected.PUT("/feature-flags", featureFlagsHandler.UpdateFlags)

		// User Profile & API Key
		protected.GET("/user/profile", userHandler.GetProfile)
		protected.POST("/user/profile", userHandler.UpdateProfile)
		protected.POST("/user/api-key", userHandler.RegenerateAPIKey)

		// User Management (admin only routes are in RegisterRoutes)
		protected.GET("/users", userHandler.ListUsers)
		protected.POST("/users", userHandler.CreateUser)
		protected.POST("/users/invite", userHandler.InviteUser)
		protected.GET("/users/:id", userHandler.GetUser)
		protected.PUT("/users/:id", userHandler.UpdateUser)
		protected.DELETE("/users/:id", userHandler.DeleteUser)
		protected.PUT("/users/:id/permissions", userHandler.UpdateUserPermissions)

		// Updates
		updateService := services.NewUpdateService()
		updateHandler := handlers.NewUpdateHandler(updateService)
		protected.GET("/system/updates", updateHandler.Check)

		// System info
		systemHandler := handlers.NewSystemHandler()
		protected.GET("/system/my-ip", systemHandler.GetMyIP)

		// Notifications
		notificationHandler := handlers.NewNotificationHandler(notificationService)
		protected.GET("/notifications", notificationHandler.List)
		protected.POST("/notifications/:id/read", notificationHandler.MarkAsRead)
		protected.POST("/notifications/read-all", notificationHandler.MarkAllAsRead)

		// Domains
		domainHandler := handlers.NewDomainHandler(db, notificationService)
		protected.GET("/domains", domainHandler.List)
		protected.POST("/domains", domainHandler.Create)
		protected.DELETE("/domains/:id", domainHandler.Delete)

		// Docker
		dockerService, err := services.NewDockerService()
		if err == nil { // Only register if Docker is available
			dockerHandler := handlers.NewDockerHandler(dockerService, remoteServerService)
			dockerHandler.RegisterRoutes(protected)
		} else {
			logger.Log().WithError(err).Warn("Docker service unavailable")
		}

		// Uptime Service
		uptimeService := services.NewUptimeService(db, notificationService)
		uptimeHandler := handlers.NewUptimeHandler(uptimeService)
		protected.GET("/uptime/monitors", uptimeHandler.List)
		protected.GET("/uptime/monitors/:id/history", uptimeHandler.GetHistory)
		protected.PUT("/uptime/monitors/:id", uptimeHandler.Update)
		protected.DELETE("/uptime/monitors/:id", uptimeHandler.Delete)
		protected.POST("/uptime/monitors/:id/check", uptimeHandler.CheckMonitor)
		protected.POST("/uptime/sync", uptimeHandler.Sync)

		// Notification Providers
		notificationProviderHandler := handlers.NewNotificationProviderHandler(notificationService)
		protected.GET("/notifications/providers", notificationProviderHandler.List)
		protected.POST("/notifications/providers", notificationProviderHandler.Create)
		protected.PUT("/notifications/providers/:id", notificationProviderHandler.Update)
		protected.DELETE("/notifications/providers/:id", notificationProviderHandler.Delete)
		protected.POST("/notifications/providers/test", notificationProviderHandler.Test)
		protected.POST("/notifications/providers/preview", notificationProviderHandler.Preview)
		protected.GET("/notifications/templates", notificationProviderHandler.Templates)

		// External notification templates (saved templates for providers)
		notificationTemplateHandler := handlers.NewNotificationTemplateHandler(notificationService)
		protected.GET("/notifications/external-templates", notificationTemplateHandler.List)
		protected.POST("/notifications/external-templates", notificationTemplateHandler.Create)
		protected.PUT("/notifications/external-templates/:id", notificationTemplateHandler.Update)
		protected.DELETE("/notifications/external-templates/:id", notificationTemplateHandler.Delete)
		protected.POST("/notifications/external-templates/preview", notificationTemplateHandler.Preview)

		// Ensure uptime feature flag exists to avoid record-not-found logs
		defaultUptime := models.Setting{Key: "feature.uptime.enabled", Value: "true", Type: "bool", Category: "feature"}
		if err := db.Where(models.Setting{Key: defaultUptime.Key}).Attrs(defaultUptime).FirstOrCreate(&defaultUptime).Error; err != nil {
			logger.Log().WithError(err).Warn("Failed to ensure uptime feature flag default")
		}

		// Start background checker (every 1 minute)
		go func() {
			// Wait a bit for server to start
			time.Sleep(30 * time.Second)

			// Initial sync if enabled
			var s models.Setting
			enabled := true
			if err := db.Where("key = ?", "feature.uptime.enabled").First(&s).Error; err == nil {
				enabled = s.Value == "true"
			}

			if enabled {
				if err := uptimeService.SyncMonitors(); err != nil {
					logger.Log().WithError(err).Error("Failed to sync monitors")
				}
			}

			ticker := time.NewTicker(1 * time.Minute)
			for range ticker.C {
				// Check feature flag each tick
				enabled := true
				if err := db.Where("key = ?", "feature.uptime.enabled").First(&s).Error; err == nil {
					enabled = s.Value == "true"
				}

				if enabled {
					_ = uptimeService.SyncMonitors()
					uptimeService.CheckAll()
				}
			}
		}()

		protected.POST("/system/uptime/check", func(c *gin.Context) {
			go uptimeService.CheckAll()
			c.JSON(200, gin.H{"message": "Uptime check started"})
		})

		// Caddy Manager
		caddyClient := caddy.NewClient(cfg.CaddyAdminAPI)
		caddyManager = caddy.NewManager(caddyClient, db, cfg.CaddyConfigDir, cfg.FrontendDir, cfg.ACMEStaging, cfg.Security)

		// Initialize GeoIP service if database exists
		geoipPath := os.Getenv("CHARON_GEOIP_DB_PATH")
		if geoipPath == "" {
			geoipPath = "/app/data/geoip/GeoLite2-Country.mmdb"
		}

		var geoipSvc *services.GeoIPService
		if _, err := os.Stat(geoipPath); err == nil {
			var geoErr error
			geoipSvc, geoErr = services.NewGeoIPService(geoipPath)
			if geoErr != nil {
				logger.Log().WithError(geoErr).WithField("path", geoipPath).Warn("Failed to load GeoIP database - geo-blocking features will be unavailable")
			} else {
				logger.Log().WithField("path", geoipPath).Info("GeoIP database loaded successfully")
			}
		} else {
			logger.Log().WithField("path", geoipPath).Info("GeoIP database not found - geo-blocking features will be unavailable")
		}

		// Security Status
		securityHandler := handlers.NewSecurityHandler(cfg.Security, db, caddyManager)
		if geoipSvc != nil {
			securityHandler.SetGeoIPService(geoipSvc)
		}
		protected.GET("/security/status", securityHandler.GetStatus)
		// Security Config management
		protected.GET("/security/config", securityHandler.GetConfig)
		protected.POST("/security/config", securityHandler.UpdateConfig)
		protected.POST("/security/enable", securityHandler.Enable)
		protected.POST("/security/disable", securityHandler.Disable)
		protected.POST("/security/breakglass/generate", securityHandler.GenerateBreakGlass)
		protected.GET("/security/decisions", securityHandler.ListDecisions)
		protected.POST("/security/decisions", securityHandler.CreateDecision)
		protected.GET("/security/rulesets", securityHandler.ListRuleSets)
		protected.POST("/security/rulesets", securityHandler.UpsertRuleSet)
		protected.DELETE("/security/rulesets/:id", securityHandler.DeleteRuleSet)
		protected.GET("/security/rate-limit/presets", securityHandler.GetRateLimitPresets)
		// GeoIP endpoints
		protected.GET("/security/geoip/status", securityHandler.GetGeoIPStatus)
		protected.POST("/security/geoip/reload", securityHandler.ReloadGeoIP)
		protected.POST("/security/geoip/lookup", securityHandler.LookupGeoIP)
		// WAF exclusion endpoints
		protected.GET("/security/waf/exclusions", securityHandler.GetWAFExclusions)
		protected.POST("/security/waf/exclusions", securityHandler.AddWAFExclusion)
		protected.DELETE("/security/waf/exclusions/:rule_id", securityHandler.DeleteWAFExclusion)

		// CrowdSec process management and import
		// Data dir for crowdsec (persisted on host via volumes)
		crowdsecDataDir := cfg.Security.CrowdSecConfigDir
		crowdsecExec := handlers.NewDefaultCrowdsecExecutor()
		crowdsecHandler := handlers.NewCrowdsecHandler(db, crowdsecExec, "crowdsec", crowdsecDataDir)
		crowdsecHandler.RegisterRoutes(protected)

		// Cerberus Security Logs WebSocket
		// Initialize log watcher for Caddy access logs (used by CrowdSec and security monitoring)
		// The log path follows CrowdSec convention: /var/log/caddy/access.log in production
		// or falls back to the configured storage directory for development
		accessLogPath := os.Getenv("CHARON_CADDY_ACCESS_LOG")
		if accessLogPath == "" {
			accessLogPath = "/var/log/caddy/access.log"
		}
		logWatcher := services.NewLogWatcher(accessLogPath)
		if err := logWatcher.Start(context.Background()); err != nil {
			logger.Log().WithError(err).Error("Failed to start security log watcher")
		}
		cerberusLogsHandler := handlers.NewCerberusLogsHandler(logWatcher)
		protected.GET("/cerberus/logs/ws", cerberusLogsHandler.LiveLogs)

		// Access Lists
		accessListHandler := handlers.NewAccessListHandler(db)
		if geoipSvc != nil {
			accessListHandler.SetGeoIPService(geoipSvc)
		}
		protected.GET("/access-lists/templates", accessListHandler.GetTemplates)
		protected.GET("/access-lists", accessListHandler.List)
		protected.POST("/access-lists", accessListHandler.Create)
		protected.GET("/access-lists/:id", accessListHandler.Get)
		protected.PUT("/access-lists/:id", accessListHandler.Update)
		protected.DELETE("/access-lists/:id", accessListHandler.Delete)
		protected.POST("/access-lists/:id/test", accessListHandler.TestIP)

		// Certificate routes
		// Use cfg.CaddyConfigDir + "/data" for cert service so we scan the actual Caddy storage
		// where ACME and certificates are stored (e.g. <CaddyConfigDir>/data).
		caddyDataDir := cfg.CaddyConfigDir + "/data"
		logger.Log().WithField("caddy_data_dir", caddyDataDir).Info("Using Caddy data directory for certificates scan")
		certService := services.NewCertificateService(caddyDataDir, db)
		certHandler := handlers.NewCertificateHandler(certService, backupService, notificationService)
		protected.GET("/certificates", certHandler.List)
		protected.POST("/certificates", certHandler.Upload)
		protected.DELETE("/certificates/:id", certHandler.Delete)
	}

	// Caddy Manager already created above

	proxyHostHandler := handlers.NewProxyHostHandler(db, caddyManager, notificationService, uptimeService)
	proxyHostHandler.RegisterRoutes(api)

	remoteServerHandler := handlers.NewRemoteServerHandler(remoteServerService, notificationService)
	remoteServerHandler.RegisterRoutes(api)

	// Initial Caddy Config Sync
	go func() {
		// Wait for Caddy to be ready (max 30 seconds)
		ctx := context.Background()
		timeout := time.After(30 * time.Second)
		ticker := time.NewTicker(1 * time.Second)
		defer ticker.Stop()

		ready := false
		for {
			select {
			case <-timeout:
				logger.Log().Warn("Timeout waiting for Caddy to be ready")
				return
			case <-ticker.C:
				if err := caddyManager.Ping(ctx); err == nil {
					ready = true
					goto Apply
				}
			}
		}

	Apply:
		if ready {
			// Apply config
			if err := caddyManager.ApplyConfig(ctx); err != nil {
				logger.Log().WithError(err).Error("Failed to apply initial Caddy config")
			} else {
				logger.Log().Info("Successfully applied initial Caddy config")
			}
		}
	}()

	return nil
}

// RegisterImportHandler wires up import routes with config dependencies.
func RegisterImportHandler(router *gin.Engine, db *gorm.DB, caddyBinary, importDir, mountPath string) {
	importHandler := handlers.NewImportHandler(db, caddyBinary, importDir, mountPath)
	api := router.Group("/api/v1")
	importHandler.RegisterRoutes(api)
}
