// Package server provides HTTP and WebSocket services for the SkyView application.
//
// This package implements the web server that serves both static assets and real-time
// aircraft data via REST API endpoints and WebSocket connections. It handles:
//   - Static web file serving from embedded assets
//   - RESTful API endpoints for aircraft, sources, and statistics
//   - Real-time WebSocket streaming for live aircraft updates
//   - CORS handling for cross-origin requests
//   - Coverage and heatmap data generation for visualization
//
// The server integrates with the merger component to access consolidated aircraft
// data from multiple sources and provides various data formats optimized for
// web consumption.
package server

import (
	"context"
	"embed"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"path"
	"strconv"
	"sync"
	"time"

	"github.com/gorilla/mux"
	"github.com/gorilla/websocket"

	"skyview/internal/merger"
)

// OriginConfig represents the geographical reference point configuration.
// This is used as the center point for the web map interface and for
// distance calculations in coverage analysis.
type OriginConfig struct {
	Latitude  float64 `json:"latitude"`    // Reference latitude in decimal degrees
	Longitude float64 `json:"longitude"`   // Reference longitude in decimal degrees
	Name      string  `json:"name,omitempty"` // Descriptive name for the origin point
}

// Server handles HTTP requests and WebSocket connections for the SkyView web interface.
// It serves static web assets, provides RESTful API endpoints for aircraft data,
// and maintains real-time WebSocket connections for live updates.
//
// The server architecture uses:
//   - Gorilla mux for HTTP routing
//   - Gorilla WebSocket for real-time communication
//   - Embedded filesystem for static asset serving
//   - Concurrent broadcast system for WebSocket clients
//   - CORS support for cross-origin web applications
type Server struct {
	port        int                // TCP port for HTTP server
	merger      *merger.Merger     // Data source for aircraft information
	staticFiles embed.FS          // Embedded static web assets
	server      *http.Server       // HTTP server instance
	origin      OriginConfig       // Geographic reference point

	// WebSocket management
	wsClients   map[*websocket.Conn]bool // Active WebSocket client connections
	wsClientsMu sync.RWMutex             // Protects wsClients map
	upgrader    websocket.Upgrader       // HTTP to WebSocket protocol upgrader

	// Broadcast channels for real-time updates
	broadcastChan chan []byte      // Channel for broadcasting updates to all clients
	stopChan      chan struct{}    // Shutdown signal channel
}

// WebSocketMessage represents the standard message format for WebSocket communication.
// All messages sent to clients follow this structure to provide consistent
// message handling and enable message type discrimination on the client side.
type WebSocketMessage struct {
	Type      string      `json:"type"`      // Message type ("initial_data", "aircraft_update", etc.)
	Timestamp int64       `json:"timestamp"` // Unix timestamp when message was created
	Data      interface{} `json:"data"`      // Message payload (varies by type)
}

// AircraftUpdate represents the complete aircraft data payload sent via WebSocket.
// This structure contains all information needed by the web interface to display
// current aircraft positions, source status, and system statistics.
type AircraftUpdate struct {
	Aircraft map[string]*merger.AircraftState `json:"aircraft"` // Current aircraft keyed by ICAO hex string
	Sources  []*merger.Source                 `json:"sources"`  // Active data sources with status
	Stats    map[string]interface{}           `json:"stats"`    // System statistics and metrics
}

// NewServer creates a new HTTP server instance for serving the SkyView web interface.
//
// The server is configured with:
//   - WebSocket upgrader allowing all origins (suitable for development)
//   - Buffered broadcast channel for efficient message distribution
//   - Read/Write buffers optimized for aircraft data messages
//
// Parameters:
//   - port: TCP port number for the HTTP server
//   - merger: Data merger instance providing aircraft information
//   - staticFiles: Embedded filesystem containing web assets
//   - origin: Geographic reference point for the map interface
//
// Returns a configured but not yet started server instance.
func NewServer(port int, merger *merger.Merger, staticFiles embed.FS, origin OriginConfig) *Server {
	return &Server{
		port:        port,
		merger:      merger,
		staticFiles: staticFiles,
		origin:      origin,
		wsClients:   make(map[*websocket.Conn]bool),
		upgrader: websocket.Upgrader{
			CheckOrigin: func(r *http.Request) bool {
				return true // Allow all origins in development
			},
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		},
		broadcastChan: make(chan []byte, 100),
		stopChan:      make(chan struct{}),
	}
}

// Start begins serving HTTP requests and WebSocket connections.
//
// This method starts several background routines:
//   1. Broadcast routine - handles WebSocket message distribution
//   2. Periodic update routine - sends regular updates to WebSocket clients
//   3. HTTP server - serves API endpoints and static files
//
// The method blocks until the server encounters an error or is shut down.
// Use Stop() for graceful shutdown.
//
// Returns an error if the server fails to start or encounters a fatal error.
func (s *Server) Start() error {
	// Start broadcast routine
	go s.broadcastRoutine()

	// Start periodic updates
	go s.periodicUpdateRoutine()

	// Setup routes
	router := s.setupRoutes()

	s.server = &http.Server{
		Addr:    fmt.Sprintf(":%d", s.port),
		Handler: router,
	}

	return s.server.ListenAndServe()
}

// Stop gracefully shuts down the server and all background routines.
//
// This method:
//   1. Signals all background routines to stop via stopChan
//   2. Shuts down the HTTP server with a 5-second timeout
//   3. Closes WebSocket connections
//
// The shutdown is designed to be safe and allow in-flight requests to complete.
func (s *Server) Stop() {
	close(s.stopChan)

	if s.server != nil {
		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()
		s.server.Shutdown(ctx)
	}
}

// setupRoutes configures the HTTP routing for all server endpoints.
//
// The routing structure includes:
//   - /api/* - RESTful API endpoints for data access
//   - /ws - WebSocket endpoint for real-time updates
//   - /static/* - Static file serving
//   - / - Main application page
//
// All routes are wrapped with CORS middleware for cross-origin support.
//
// Returns a configured HTTP handler ready for use with the HTTP server.
func (s *Server) setupRoutes() http.Handler {
	router := mux.NewRouter()

	// API routes
	api := router.PathPrefix("/api").Subrouter()
	api.HandleFunc("/aircraft", s.handleGetAircraft).Methods("GET")
	api.HandleFunc("/aircraft/{icao}", s.handleGetAircraftDetails).Methods("GET")
	api.HandleFunc("/debug/aircraft", s.handleDebugAircraft).Methods("GET")
	api.HandleFunc("/sources", s.handleGetSources).Methods("GET")
	api.HandleFunc("/stats", s.handleGetStats).Methods("GET")
	api.HandleFunc("/origin", s.handleGetOrigin).Methods("GET")
	api.HandleFunc("/coverage/{sourceId}", s.handleGetCoverage).Methods("GET")
	api.HandleFunc("/heatmap/{sourceId}", s.handleGetHeatmap).Methods("GET")

	// WebSocket
	router.HandleFunc("/ws", s.handleWebSocket)

	// Static files
	router.PathPrefix("/static/").Handler(s.staticFileHandler())
	router.HandleFunc("/favicon.ico", s.handleFavicon)

	// Main page
	router.HandleFunc("/", s.handleIndex)

	// Enable CORS
	return s.enableCORS(router)
}

// isAircraftUseful determines if an aircraft has enough data to be useful for the frontend.
//
// DESIGN NOTE: We WANT reasonable aircraft to appear in our table view, even if they 
// don't have enough data to appear on the map. This provides users visibility into 
// all tracked aircraft, not just those with complete position data.
//
// Aircraft are considered useful if they have ANY of:
//   - Valid position data (both latitude and longitude non-zero) -> Can show on map
//   - Callsign (flight identification) -> Can show in table with "No position" status  
//   - Altitude information -> Can show in table as "Aircraft at X feet"
//   - Any other identifying information that makes it a "real" aircraft
//
// This inclusive approach ensures the table view shows all aircraft we're tracking,
// while the map view only shows those with valid positions (handled by frontend filtering).
func (s *Server) isAircraftUseful(aircraft *merger.AircraftState) bool {
	// Aircraft is useful if it has any meaningful data:
	hasValidPosition := aircraft.Latitude != 0 && aircraft.Longitude != 0
	hasCallsign := aircraft.Callsign != ""
	hasAltitude := aircraft.Altitude != 0
	hasSquawk := aircraft.Squawk != ""
	
	// Include aircraft with any identifying or operational data
	return hasValidPosition || hasCallsign || hasAltitude || hasSquawk
}

// handleGetAircraft serves the /api/aircraft endpoint.
// Returns all currently tracked aircraft with their latest state information.
//
// Only "useful" aircraft are returned - those with position data or callsign.
// This filters out incomplete aircraft that only have altitude or squawk codes,
// which are not actionable for frontend mapping and flight tracking.
//
// The response includes:
//   - timestamp: Unix timestamp of the response
//   - aircraft: Map of aircraft keyed by ICAO hex strings
//   - count: Total number of useful aircraft (filtered count)
//
// Aircraft ICAO addresses are converted from uint32 to 6-digit hex strings
// for consistent JSON representation (e.g., 0xABC123 -> "ABC123").
func (s *Server) handleGetAircraft(w http.ResponseWriter, r *http.Request) {
	aircraft := s.merger.GetAircraft()

	// Convert ICAO keys to hex strings for JSON and filter useful aircraft
	aircraftMap := make(map[string]*merger.AircraftState)
	for icao, state := range aircraft {
		if s.isAircraftUseful(state) {
			aircraftMap[fmt.Sprintf("%06X", icao)] = state
		}
	}

	response := map[string]interface{}{
		"timestamp": time.Now().Unix(),
		"aircraft":  aircraftMap,
		"count":     len(aircraftMap), // Count of filtered useful aircraft
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}

// handleGetAircraftDetails serves the /api/aircraft/{icao} endpoint.
// Returns detailed information for a specific aircraft identified by ICAO address.
//
// The ICAO parameter should be a 6-digit hexadecimal string (e.g., "ABC123").
// Returns 400 Bad Request for invalid ICAO format.
// Returns 404 Not Found if the aircraft is not currently tracked.
//
// On success, returns the complete AircraftState for the requested aircraft.
func (s *Server) handleGetAircraftDetails(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	icaoStr := vars["icao"]

	// Parse ICAO hex string
	icao, err := strconv.ParseUint(icaoStr, 16, 32)
	if err != nil {
		http.Error(w, "Invalid ICAO address", http.StatusBadRequest)
		return
	}

	aircraft := s.merger.GetAircraft()
	if state, exists := aircraft[uint32(icao)]; exists {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(state)
	} else {
		http.Error(w, "Aircraft not found", http.StatusNotFound)
	}
}

// handleGetSources serves the /api/sources endpoint.
// Returns information about all configured data sources and their current status.
//
// The response includes:
//   - sources: Array of source configurations with connection status
//   - count: Total number of configured sources
//
// This endpoint is useful for monitoring source connectivity and debugging
// multi-source setups.
func (s *Server) handleGetSources(w http.ResponseWriter, r *http.Request) {
	sources := s.merger.GetSources()

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"sources": sources,
		"count":   len(sources),
	})
}

// handleGetStats serves the /api/stats endpoint.
// Returns system statistics and performance metrics from the data merger.
//
// Statistics may include:
//   - Message processing rates
//   - Aircraft count by source
//   - Connection status
//   - Data quality metrics
//
// The exact statistics depend on the merger implementation.
func (s *Server) handleGetStats(w http.ResponseWriter, r *http.Request) {
	stats := s.merger.GetStatistics()

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(stats)
}

// handleGetOrigin serves the /api/origin endpoint.
// Returns the configured geographical reference point used by the system.
//
// The origin point is used for:
//   - Default map center in the web interface
//   - Distance calculations in coverage analysis
//   - Range circle calculations
func (s *Server) handleGetOrigin(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(s.origin)
}

// handleGetCoverage serves the /api/coverage/{sourceId} endpoint.
// Returns coverage data for a specific source based on aircraft positions and signal strength.
//
// The coverage data includes all positions where the specified source has received
// aircraft signals, along with signal strength and distance information.
// This is useful for visualizing receiver coverage patterns and range.
//
// Parameters:
//   - sourceId: URL parameter identifying the source
//
// Returns array of coverage points with lat/lon, signal strength, distance, and altitude.
func (s *Server) handleGetCoverage(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	sourceID := vars["sourceId"]

	// Generate coverage data based on signal strength
	aircraft := s.merger.GetAircraft()
	coveragePoints := make([]map[string]interface{}, 0)

	for _, state := range aircraft {
		if srcData, exists := state.Sources[sourceID]; exists {
			coveragePoints = append(coveragePoints, map[string]interface{}{
				"lat":      state.Latitude,
				"lon":      state.Longitude,
				"signal":   srcData.SignalLevel,
				"distance": srcData.Distance,
				"altitude": state.Altitude,
			})
		}
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(map[string]interface{}{
		"source": sourceID,
		"points": coveragePoints,
	})
}

// handleGetHeatmap serves the /api/heatmap/{sourceId} endpoint.
// Generates a grid-based heatmap visualization of signal coverage for a specific source.
//
// The heatmap is computed by:
//   1. Finding geographic bounds of all aircraft positions for the source
//   2. Creating a 100x100 grid covering the bounds
//   3. Accumulating signal strength values in each grid cell
//   4. Returning the grid data with boundary coordinates
//
// This provides a density-based visualization of where the source receives
// the strongest signals, useful for coverage analysis and antenna optimization.
//
// Parameters:
//   - sourceId: URL parameter identifying the source
//
// Returns grid data array and geographic bounds for visualization.
func (s *Server) handleGetHeatmap(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	sourceID := vars["sourceId"]

	// Generate heatmap data grid
	aircraft := s.merger.GetAircraft()
	heatmapData := make(map[string]interface{})

	// Simple grid-based heatmap
	grid := make([][]float64, 100)
	for i := range grid {
		grid[i] = make([]float64, 100)
	}

	// Find bounds
	minLat, maxLat := 90.0, -90.0
	minLon, maxLon := 180.0, -180.0

	for _, state := range aircraft {
		if _, exists := state.Sources[sourceID]; exists {
			if state.Latitude < minLat {
				minLat = state.Latitude
			}
			if state.Latitude > maxLat {
				maxLat = state.Latitude
			}
			if state.Longitude < minLon {
				minLon = state.Longitude
			}
			if state.Longitude > maxLon {
				maxLon = state.Longitude
			}
		}
	}

	// Fill grid
	for _, state := range aircraft {
		if srcData, exists := state.Sources[sourceID]; exists {
			latIdx := int((state.Latitude - minLat) / (maxLat - minLat) * 99)
			lonIdx := int((state.Longitude - minLon) / (maxLon - minLon) * 99)

			if latIdx >= 0 && latIdx < 100 && lonIdx >= 0 && lonIdx < 100 {
				grid[latIdx][lonIdx] += srcData.SignalLevel
			}
		}
	}

	heatmapData["grid"] = grid
	heatmapData["bounds"] = map[string]float64{
		"minLat": minLat,
		"maxLat": maxLat,
		"minLon": minLon,
		"maxLon": maxLon,
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(heatmapData)
}

// handleWebSocket manages WebSocket connections for real-time aircraft data streaming.
//
// This handler:
//   1. Upgrades the HTTP connection to WebSocket protocol
//   2. Registers the client for broadcast updates
//   3. Sends initial data snapshot to the client
//   4. Handles client messages (currently just ping/pong for keepalive)
//   5. Cleans up the connection when the client disconnects
//
// WebSocket clients receive periodic updates with current aircraft positions,
// source status, and system statistics. The connection is kept alive until
// the client disconnects or the server shuts down.
func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) {
	conn, err := s.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Printf("WebSocket upgrade error: %v", err)
		return
	}
	defer conn.Close()

	// Register client
	s.wsClientsMu.Lock()
	s.wsClients[conn] = true
	s.wsClientsMu.Unlock()

	// Send initial data
	s.sendInitialData(conn)

	// Handle client messages (ping/pong)
	for {
		_, _, err := conn.ReadMessage()
		if err != nil {
			break
		}
	}

	// Unregister client
	s.wsClientsMu.Lock()
	delete(s.wsClients, conn)
	s.wsClientsMu.Unlock()
}

// sendInitialData sends a complete data snapshot to a newly connected WebSocket client.
//
// This includes:
//   - All currently tracked aircraft with their state information
//   - Status of all configured data sources
//   - Current system statistics
//
// ICAO addresses are converted to hex strings for consistent JSON representation.
// This initial data allows the client to immediately display current aircraft
// without waiting for the next periodic update.
func (s *Server) sendInitialData(conn *websocket.Conn) {
	aircraft := s.merger.GetAircraft()
	sources := s.merger.GetSources()
	stats := s.merger.GetStatistics()

	// Convert ICAO keys to hex strings and filter useful aircraft
	aircraftMap := make(map[string]*merger.AircraftState)
	for icao, state := range aircraft {
		if s.isAircraftUseful(state) {
			aircraftMap[fmt.Sprintf("%06X", icao)] = state
		}
	}

	update := AircraftUpdate{
		Aircraft: aircraftMap,
		Sources:  sources,
		Stats:    stats,
	}

	msg := WebSocketMessage{
		Type:      "initial_data",
		Timestamp: time.Now().Unix(),
		Data:      update,
	}

	conn.WriteJSON(msg)
}

// broadcastRoutine runs in a dedicated goroutine to distribute WebSocket messages.
//
// This routine:
//   - Listens for broadcast messages on the broadcastChan
//   - Sends messages to all connected WebSocket clients
//   - Handles client connection cleanup on write errors
//   - Respects the shutdown signal from stopChan
//
// Using a dedicated routine for broadcasting ensures efficient message
// distribution without blocking the update generation.
func (s *Server) broadcastRoutine() {
	for {
		select {
		case <-s.stopChan:
			return
		case data := <-s.broadcastChan:
			s.wsClientsMu.RLock()
			for conn := range s.wsClients {
				if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
					conn.Close()
					delete(s.wsClients, conn)
				}
			}
			s.wsClientsMu.RUnlock()
		}
	}
}

// periodicUpdateRoutine generates regular WebSocket updates for all connected clients.
//
// Updates are sent every second and include:
//   - Current aircraft positions and state
//   - Data source status updates
//   - Fresh system statistics
//
// The routine uses a ticker for consistent timing and respects the shutdown
// signal. Updates are queued through broadcastUpdate() which handles the
// actual message formatting and distribution.
func (s *Server) periodicUpdateRoutine() {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-s.stopChan:
			return
		case <-ticker.C:
			s.broadcastUpdate()
		}
	}
}

// broadcastUpdate creates and queues an aircraft update message for WebSocket clients.
//
// This function:
//   1. Collects current aircraft data from the merger
//   2. Filters aircraft to only include "useful" ones (with position or callsign)
//   3. Formats the data as a WebSocketMessage with type "aircraft_update"
//   4. Converts ICAO addresses to hex strings for JSON compatibility
//   5. Queues the message for broadcast (non-blocking)
//
// If the broadcast channel is full, the update is dropped to prevent blocking.
// This ensures the system continues operating even if WebSocket clients
// cannot keep up with updates.
func (s *Server) broadcastUpdate() {
	aircraft := s.merger.GetAircraft()
	sources := s.merger.GetSources()
	stats := s.merger.GetStatistics()

	// Convert ICAO keys to hex strings and filter useful aircraft
	aircraftMap := make(map[string]*merger.AircraftState)
	for icao, state := range aircraft {
		if s.isAircraftUseful(state) {
			aircraftMap[fmt.Sprintf("%06X", icao)] = state
		}
	}

	update := AircraftUpdate{
		Aircraft: aircraftMap,
		Sources:  sources,
		Stats:    stats,
	}

	msg := WebSocketMessage{
		Type:      "aircraft_update",
		Timestamp: time.Now().Unix(),
		Data:      update,
	}

	if data, err := json.Marshal(msg); err == nil {
		select {
		case s.broadcastChan <- data:
		default:
			// Channel full, skip this update
		}
	}
}

// handleIndex serves the main application page at the root URL.
// Returns the embedded index.html file which contains the aircraft tracking interface.
//
// Returns 404 if the index.html file is not found in the embedded assets.
func (s *Server) handleIndex(w http.ResponseWriter, r *http.Request) {
	data, err := s.staticFiles.ReadFile("static/index.html")
	if err != nil {
		http.Error(w, "Page not found", http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "text/html")
	w.Write(data)
}

// handleFavicon serves the favicon.ico file for browser tab icons.
// Returns the embedded favicon file with appropriate content-type header.
//
// Returns 404 if the favicon.ico file is not found in the embedded assets.
func (s *Server) handleFavicon(w http.ResponseWriter, r *http.Request) {
	data, err := s.staticFiles.ReadFile("static/favicon.ico")
	if err != nil {
		http.Error(w, "Favicon not found", http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "image/x-icon")
	w.Write(data)
}

// staticFileHandler creates an HTTP handler for serving embedded static files.
//
// This handler:
//   - Maps URL paths from /static/* to embedded file paths
//   - Sets appropriate Content-Type headers based on file extension
//   - Adds cache control headers for client-side caching (1 hour)
//   - Returns 404 for missing files
//
// The handler serves files from the embedded filesystem, enabling
// single-binary deployment without external static file dependencies.
func (s *Server) staticFileHandler() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Remove /static/ prefix from URL path to get the actual file path
		filePath := "static" + r.URL.Path[len("/static"):]

		data, err := s.staticFiles.ReadFile(filePath)
		if err != nil {
			http.NotFound(w, r)
			return
		}

		// Set content type
		ext := path.Ext(filePath)
		contentType := getContentType(ext)
		w.Header().Set("Content-Type", contentType)

		// Cache control
		w.Header().Set("Cache-Control", "public, max-age=3600")

		w.Write(data)
	})
}

// getContentType returns the appropriate MIME type for a file extension.
// Supports common web file types used in the SkyView interface:
//   - HTML, CSS, JavaScript files
//   - JSON data files
//   - Image formats (SVG, PNG, JPEG, ICO)
//
// Returns "application/octet-stream" for unknown extensions.
func getContentType(ext string) string {
	switch ext {
	case ".html":
		return "text/html"
	case ".css":
		return "text/css"
	case ".js":
		return "application/javascript"
	case ".json":
		return "application/json"
	case ".svg":
		return "image/svg+xml"
	case ".png":
		return "image/png"
	case ".jpg", ".jpeg":
		return "image/jpeg"
	case ".ico":
		return "image/x-icon"
	default:
		return "application/octet-stream"
	}
}

// enableCORS wraps an HTTP handler with Cross-Origin Resource Sharing headers.
//
// This middleware:
//   - Allows requests from any origin (*)
//   - Supports GET, POST, PUT, DELETE, and OPTIONS methods
//   - Permits Content-Type and Authorization headers
//   - Handles preflight OPTIONS requests
//
// CORS is enabled to support web applications hosted on different domains
// than the SkyView server, which is common in development and some deployment scenarios.
func (s *Server) enableCORS(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}

		handler.ServeHTTP(w, r)
	})
}

// handleDebugAircraft serves the /api/debug/aircraft endpoint.
// Returns all aircraft (filtered and unfiltered) for debugging position issues.
func (s *Server) handleDebugAircraft(w http.ResponseWriter, r *http.Request) {
	aircraft := s.merger.GetAircraft()

	// All aircraft (unfiltered)
	allAircraftMap := make(map[string]*merger.AircraftState)
	for icao, state := range aircraft {
		allAircraftMap[fmt.Sprintf("%06X", icao)] = state
	}

	// Filtered aircraft (useful ones)
	filteredAircraftMap := make(map[string]*merger.AircraftState)
	for icao, state := range aircraft {
		if s.isAircraftUseful(state) {
			filteredAircraftMap[fmt.Sprintf("%06X", icao)] = state
		}
	}

	response := map[string]interface{}{
		"timestamp":        time.Now().Unix(),
		"all_aircraft":     allAircraftMap,
		"filtered_aircraft": filteredAircraftMap,
		"all_count":        len(allAircraftMap),
		"filtered_count":   len(filteredAircraftMap),
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(response)
}
