package server

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"

	"github.com/Wikid82/charon/backend/internal/api/handlers"
	"github.com/Wikid82/charon/backend/internal/config"
	"github.com/Wikid82/charon/backend/internal/logger"
	"github.com/Wikid82/charon/backend/internal/util"
)

// EmergencyServer provides a minimal HTTP server for emergency operations.
// This server runs on a separate port with minimal security for failsafe access.
//
// Port Assignment:
// - Port 2019: Reserved for Caddy admin API
// - Port 2020: Emergency server (tier-2 break glass)
//
// Security Philosophy:
// - Separate port bypasses Caddy/CrowdSec/WAF entirely
// - Optional Basic Auth (configurable via env)
// - Should ONLY be accessible via VPN/SSH tunnel
// - Default bind to localhost IPv4 (127.0.0.1:2020) for safety
// - IPv6 support available via config (e.g., 0.0.0.0:2020 or [::]:2020 for dual-stack)
//
// Use Cases:
// - Layer 7 reverse proxy blocking requests (CrowdSec bouncer at Caddy)
// - Caddy itself is down or misconfigured
// - Emergency access when main application port is unreachable
type EmergencyServer struct {
	server   *http.Server
	listener net.Listener
	db       *gorm.DB
	cfg      config.EmergencyConfig
	cerberus handlers.CacheInvalidator
	caddy    handlers.CaddyConfigManager
}

// NewEmergencyServer creates a new emergency server instance
func NewEmergencyServer(db *gorm.DB, cfg config.EmergencyConfig) *EmergencyServer {
	return NewEmergencyServerWithDeps(db, cfg, nil, nil)
}

// NewEmergencyServerWithDeps creates a new emergency server instance with optional dependencies.
func NewEmergencyServerWithDeps(db *gorm.DB, cfg config.EmergencyConfig, caddyManager handlers.CaddyConfigManager, cerberus handlers.CacheInvalidator) *EmergencyServer {
	return &EmergencyServer{
		db:       db,
		cfg:      cfg,
		caddy:    caddyManager,
		cerberus: cerberus,
	}
}

// Start initializes and starts the emergency server
func (s *EmergencyServer) Start() error {
	if !s.cfg.Enabled {
		logger.Log().Info("Emergency server disabled (CHARON_EMERGENCY_SERVER_ENABLED=false)")
		return nil
	}

	// CRITICAL: Validate emergency token is configured (fail-fast)
	emergencyToken := os.Getenv(handlers.EmergencyTokenEnvVar)
	if emergencyToken == "" || len(strings.TrimSpace(emergencyToken)) == 0 {
		logger.Log().Error("FATAL: CHARON_EMERGENCY_SERVER_ENABLED=true but CHARON_EMERGENCY_TOKEN is empty or whitespace. Emergency server cannot start without a valid token.")
		return fmt.Errorf("emergency token not configured")
	}

	// Validate token meets minimum length requirement
	if len(emergencyToken) < handlers.MinTokenLength {
		logger.Log().WithField("length", len(emergencyToken)).Warn("⚠️  WARNING: CHARON_EMERGENCY_TOKEN is shorter than 32 bytes (weak security)")
	}

	// Log token initialization with redaction
	redactedToken := redactToken(emergencyToken)
	logger.Log().WithFields(map[string]interface{}{
		"token": redactedToken,
	}).Info("Emergency server initialized with token")

	// Security warning if no authentication configured
	if s.cfg.BasicAuthUsername == "" || s.cfg.BasicAuthPassword == "" {
		logger.Log().Warn("⚠️  SECURITY WARNING: Emergency server has NO authentication configured")
		logger.Log().Warn("⚠️  Ensure port is accessible ONLY via VPN/SSH tunnel")
		logger.Log().Warn("⚠️  Set CHARON_EMERGENCY_USERNAME and CHARON_EMERGENCY_PASSWORD")
	}

	// Configure Gin for minimal logging (not production mode to preserve logs)
	router := gin.New()

	// Middleware 1: Recovery (panic handler)
	router.Use(gin.Recovery())

	// Middleware 2: Simple request logging (minimal)
	router.Use(func(c *gin.Context) {
		start := time.Now()
		path := c.Request.URL.Path
		method := c.Request.Method

		c.Next()

		latency := time.Since(start).Milliseconds()
		status := c.Writer.Status()

		logger.Log().WithFields(map[string]interface{}{
			"server":  "emergency",
			"method":  util.SanitizeForLog(method),
			"path":    util.SanitizeForLog(path),
			"status":  status,
			"latency": fmt.Sprintf("%dms", latency),
			"ip":      util.SanitizeForLog(c.ClientIP()),
		}).Info("Emergency server request")
	})

	// Emergency endpoints only
	emergencyHandler := handlers.NewEmergencyHandlerWithDeps(s.db, s.caddy, s.cerberus)

	// GET /health - Health check endpoint (NO AUTH - must be accessible for monitoring)
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status": "ok",
			"server": "emergency",
			"time":   time.Now().UTC().Format(time.RFC3339),
		})
	})

	// Middleware 3: Basic Auth (if configured)
	// Applied AFTER /health endpoint so health checks don't require auth
	if s.cfg.BasicAuthUsername != "" && s.cfg.BasicAuthPassword != "" {
		accounts := gin.Accounts{
			s.cfg.BasicAuthUsername: s.cfg.BasicAuthPassword,
		}
		router.Use(gin.BasicAuth(accounts))
		logger.Log().WithField("username", util.SanitizeForLog(s.cfg.BasicAuthUsername)).Info("Emergency server Basic Auth enabled")
	}

	// POST /emergency/security-reset - Disable all security modules
	router.POST("/emergency/security-reset", emergencyHandler.SecurityReset)

	// Create HTTP server with sensible timeouts
	s.server = &http.Server{
		Handler:      router,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  30 * time.Second,
	}

	// Create listener (this allows us to get the actual port when using :0 for testing)
	listener, err := net.Listen("tcp", s.cfg.BindAddress)
	if err != nil {
		return fmt.Errorf("failed to create listener: %w", err)
	}
	s.listener = listener

	// Start server in goroutine
	go func() {
		logger.Log().WithFields(map[string]interface{}{
			"address":  listener.Addr().String(),
			"auth":     s.cfg.BasicAuthUsername != "",
			"endpoint": "/emergency/security-reset",
		}).Info("Starting emergency server (Tier 2 break glass)")

		if err := s.server.Serve(listener); err != nil && err != http.ErrServerClosed {
			logger.Log().WithError(err).Error("Emergency server failed")
		}
	}()

	return nil
}

// Stop gracefully shuts down the emergency server
func (s *EmergencyServer) Stop(ctx context.Context) error {
	if s.server == nil {
		return nil
	}

	logger.Log().Info("Stopping emergency server")
	if err := s.server.Shutdown(ctx); err != nil {
		return fmt.Errorf("emergency server shutdown: %w", err)
	}

	logger.Log().Info("Emergency server stopped")
	return nil
}

// GetAddr returns the actual bind address (useful for tests with :0)
func (s *EmergencyServer) GetAddr() string {
	if s.listener == nil {
		return ""
	}
	return s.listener.Addr().String()
}

// redactToken returns a redacted version of the token showing only first/last 4 characters
// Format: [EMERGENCY_TOKEN:f51d...346b]
func redactToken(token string) string {
	if token == "" {
		return "[EMERGENCY_TOKEN:empty]"
	}
	if len(token) <= 8 {
		return "[EMERGENCY_TOKEN:***]"
	}
	return fmt.Sprintf("[EMERGENCY_TOKEN:%s...%s]", token[:4], token[len(token)-4:])
}
