#!/usr/bin/env python3
"""
Abflug Client Sender - Forward Aerofly GPS Data to Multiplayer Server (API v2.0)

This script receives GPS data from flight simulators (UDP or FSWidget TCP) and
forwards it to the Abflug multiplayer server via HTTP API. It acts as the "uplink" part of the multiplayer system.

Includes ATC Mode with fixed position functionality.
When ATC Mode is enabled, the client sends a fixed position instead of simulator GPS data.
This is intended for control tower operators who use the ATC_[ICAO]_[FUNCTION] callsign format.

Usage:
    python3 abflug_client_web_atc.py --api-key YOUR_API_KEY --server SERVER_URL --send-to-discord WEBHOOK_URL
    Example:
    python3 abflug_client_web_atc.py --api-key 1234567890 --server https://abflug.cloud --send-to-discord https://discord.com/api/webhooks/1455898702823555237/d_ojHQLAyTY8TJixPyHVyOzi1e-EpNjIkEMCt0ILXtsUlRFUbtVBlJSh8nSsDRAjWr_k
"""

import threading
import time
import argparse
import requests
from typing import Optional, Dict, Any
from datetime import datetime
# Import shared simulator data source (local import)
from simulator_data_source import SimulatorDataSource, DataSourceType, GPSData
from aircraft_status import aircraft as aircraft_status
from aircraft_status import airport as airport_status
from config_utils import get_butter_api_key, get_server_url
# Constants
DEFAULT_AEROFLY_PORT = 49002  # Port to receive data from Aerofly
DEFAULT_SERVER_PORT = 80       # HTTP port (or 443 for HTTPS)
DEFAULT_SERVER_PATH = "/api/position"  # API endpoint for position updates
UPDATE_INTERVAL = 1.0  # Seconds between server updates (rate limiting)
HTTP_TIMEOUT = 5.0  # HTTP request timeout in seconds
# How long to wait without new simulator data before pausing sends (seconds)
STALE_GPS_TIMEOUT = 5.0
FSWIDGET_PORT = 58585  # Fixed port for FSWidget TCP protocol
APP_VERSION = "2.6.0"
APP_NAME = "Abflug Client CLI v2.6.0"
from discord_hooks_module import prepare_flight_data_for_discord, get_color_code
# IMPORTANT: Simulator data source converts altitude to feet if it is in meters from UDP data source
# NO NEED TO CONVERT ALTITUDE IN AIRCRAFT STATUS

# Config file is no longer used - runways and gates are passed as empty lists

class AbflugClient:
    """Client that forwards simulator GPS data to the multiplayer server."""
    
    def __init__(self, api_key: str, server_url: str, aerofly_port: int = DEFAULT_AEROFLY_PORT,
                 use_https: bool = False, callsign: Optional[str] = None,
                 use_fswidget: bool = False, fswidget_ip: str = "localhost", send_to_discord: Optional[str] = None,
                 departure_airport: Optional[str] = None, arrival_airport: Optional[str] = None,
                 airplane_model: Optional[str] = None, airplane_livery: Optional[str] = None,
                 cruise_altitude: Optional[int] = None, auto_detect: bool = False,
                 flightplan_route: Optional[str] = None, squawk_code: Optional[str] = None,
                 # ATC Mode parameters
                 atc_mode_enabled: bool = False,
                 atc_latitude: Optional[float] = None,
                 atc_longitude: Optional[float] = None,
                 atc_altitude: Optional[float] = None,
                 atc_heading: Optional[float] = None,
                 atc_ground_speed: Optional[float] = None,
                 atc_vertical_speed: Optional[float] = None):
        self.api_key = api_key
        self.callsign = callsign  # Optional user-provided callsign (will be sent via flight plan)
        # Parse server URL (can be IP or domain, with or without port)
        print(f"Server URL: {server_url}")
        print(f"Departure Airport: {departure_airport}")
        print(f"Arrival Airport: {arrival_airport}")
        if not server_url.startswith(('http://', 'https://')):
            protocol = 'https://' if use_https else 'http://'
            server_url = f"{protocol}{server_url}"
        # Ensure URL doesn't end with /
        self.server_url = server_url.rstrip('/')
        self.api_url = f"{self.server_url}{DEFAULT_SERVER_PATH}"
        # Store Discord webhook URL (None or empty string means disabled)
        self.discord_webhook_url = send_to_discord if send_to_discord and send_to_discord.strip() else None
        
        # Flight plan data - stored for flight plan registration
        self.departure_airport_code = departure_airport
        self.arrival_airport_code = arrival_airport
        self.airplane_model = airplane_model
        self.airplane_livery = airplane_livery
        # Cruise altitude in feet (default: 30000 if not provided)
        self.cruise_altitude = cruise_altitude if cruise_altitude is not None else 30000
        # Flight plan route text (API v2.2) - stored as raw text, sanitized when sent
        self.flightplan_route = flightplan_route
        # Squawk code (API v2.2) - validated and padded to 4 digits
        self.squawk_code = self.validate_and_pad_squawk_code(squawk_code) if squawk_code else "0000"
        
        # ATC Mode - Fixed Position (ATC version)
        self.atc_mode_enabled = atc_mode_enabled
        self.atc_latitude = atc_latitude
        self.atc_longitude = atc_longitude
        self.atc_altitude = atc_altitude if atc_altitude is not None else 0.0
        self.atc_heading = atc_heading if atc_heading is not None else 0.0
        self.atc_ground_speed = atc_ground_speed if atc_ground_speed is not None else 0.0
        self.atc_vertical_speed = atc_vertical_speed if atc_vertical_speed is not None else 0.0
        
        # Initialize simulator data source (only if ATC mode is not enabled)
        if not self.atc_mode_enabled:
            source_type = DataSourceType.FSWIDGET if use_fswidget else DataSourceType.UDP
            self.data_source = SimulatorDataSource(
                source_type=source_type,
                udp_port=aerofly_port,
                fswidget_ip=fswidget_ip,
                fswidget_port=FSWIDGET_PORT,
                auto_detect=auto_detect
            )
        else:
            # ATC mode: data source is not needed, but create a dummy one to avoid errors
            self.data_source = None
        
        # Aircraft status tracking
        self.aircraft_status_obj: Optional[aircraft_status] = None
        self.current_aircraft_status = "unknown"
        
        # Discord message flags
        self.sent_departure_message = False
        self.sent_arrival_message = False
        self.sent_taxiing_message = False
        
        # State
        self.running = False
        self.stale_disconnect_sent: bool = False
        self.last_send_time = 0
        
        # HTTP session for connection reuse
        self.http_session = requests.Session()
        # Explicitly enable SSL certificate verification to prevent MITM attacks
        self.http_session.verify = True
        self.http_session.headers.update({
            'X-API-Key': self.api_key,
            'Content-Type': 'application/json'
        })
        
        # Threads
        self.send_thread: Optional[threading.Thread] = None
        
        # Statistics
        self.messages_sent = 0
        self.http_errors = 0
        self.start_time = time.time()
    
    @staticmethod
    def sanitize_flightplan_route(route: Optional[str]) -> str:
        """
        Sanitize flight plan route text according to API v2.2 specification.
        
        Rules:
        - Max 500 characters before sanitization
        - Only alphanumeric characters, spaces, periods (.), and forward slashes (/) allowed
        - Other non-alphanumeric characters are stripped
        - Leading and trailing spaces are trimmed
        - If empty, missing, or all spaces after sanitization, returns "NO DATA"
        
        Args:
            route: Raw route text from user input
            
        Returns:
            Sanitized route text or "NO DATA" if invalid/empty
        """
        if not route:
            return "NO DATA"
        
        # Truncate to 500 characters if longer
        route = route[:500]
        
        # Strip non-alphanumeric characters (except spaces, periods, and forward slashes)
        sanitized = ''.join(c if c.isalnum() or c.isspace() or c == '.' or c == '/' else '' for c in route)
        
        # Trim leading and trailing spaces
        sanitized = sanitized.strip()
        
        # If empty or all spaces after sanitization, return "NO DATA"
        if not sanitized:
            return "NO DATA"
        
        return sanitized
    
    @staticmethod
    def validate_and_pad_squawk_code(squawk: Optional[str]) -> str:
        """
        Validate and pad squawk code according to API v2.2 specification.
        
        Rules:
        - Exactly 4 digits (0000-9999)
        - Handled as string
        - Left-padded with zeros if less than 4 digits
        - Defaults to "0000" if invalid or not set
        
        Args:
            squawk: Raw squawk code from user input
            
        Returns:
            Validated and padded squawk code (always 4 digits) or "0000" if invalid
        """
        if not squawk:
            return "0000"
        
        # Remove any whitespace
        squawk = squawk.strip()
        
        # If empty after stripping, return default
        if not squawk:
            return "0000"
        
        # Try to parse as integer to validate it's all digits
        try:
            squawk_int = int(squawk)
            # Validate range
            if squawk_int < 0 or squawk_int > 9999:
                return "0000"
            # Left-pad with zeros to 4 digits
            return f"{squawk_int:04d}"
        except ValueError:
            # Not a valid integer, return default
            return "0000"
    
    def get_airport_info(self, icao_code: str) -> Optional[Dict[str, Any]]:
        """
        Get airport information from the server API.
        
        Args:
            icao_code: ICAO airport code (e.g., "KSFO")
        
        Returns:
            Dictionary with airport data, or None if not found/error
        """
        try:
            response = self.http_session.get(
                f"{self.server_url}/api/airport",
                params={'icao_code': icao_code},
                timeout=HTTP_TIMEOUT
            )
            response.raise_for_status()
            return response.json()
        except requests.exceptions.RequestException as e:
            if hasattr(e, 'response') and e.response is not None:
                if e.response.status_code == 404:
                    print(f"Airport {icao_code} not found in server database")
                else:
                    print(f"Error fetching airport info for {icao_code}: {e.response.status_code} - {e.response.text[:100]}")
            else:
                print(f"Error fetching airport info for {icao_code}: {e}")
            return None
    
    def register_flight_plan(self) -> bool:
        """
        Register flight plan with the server (API v2.2 includes flightplan_route).
        
        In ATC mode: Use airplane_model (ICAO) for both departure and arrival airports
        to satisfy server validation, while keeping model/livery for tower detection.
        
        Returns:
            True if successful, False otherwise
        """
        # Build flight plan payload (all fields optional)
        flight_plan = {}
        
        # In ATC mode, use airplane_model (ICAO) for both departure and arrival
        # to satisfy server validation (server requires valid ICAO codes)
        if self.atc_mode_enabled and self.airplane_model:
            # Use model as ICAO for both airports (validates on server)
            import re
            icao_pattern = re.compile(r'^[A-Z0-9]{3,4}$')
            if icao_pattern.match(self.airplane_model.upper()):
                flight_plan['departure_airport'] = self.airplane_model.upper()
                flight_plan['arrival_airport'] = self.airplane_model.upper()
                print(f"[DEBUG ATC] ATC mode: Using model '{self.airplane_model}' as ICAO for both departure and arrival airports")
        else:
            # Normal mode: use provided departure/arrival airports
            if self.departure_airport_code:
                flight_plan['departure_airport'] = self.departure_airport_code.upper()
            if self.arrival_airport_code:
                flight_plan['arrival_airport'] = self.arrival_airport_code.upper()
        
        if self.airplane_model:
            flight_plan['airplane_model'] = self.airplane_model
        if self.airplane_livery:
            flight_plan['airplane_livery'] = self.airplane_livery
        if self.callsign:
            flight_plan['callsign'] = self.callsign
        
        # Add flightplan_route (API v2.2) - sanitize before sending
        if self.flightplan_route is not None:
            sanitized_route = self.sanitize_flightplan_route(self.flightplan_route)
            flight_plan['flightplan_route'] = sanitized_route
        
        # Only send if we have at least one field
        if not flight_plan:
            print("[DEBUG ATC] No flight plan data to register (all fields optional)")
            return True  # Not an error, just no data
        
        # DEBUG: Log flight plan being sent
        print(f"[DEBUG ATC] Sending flight plan: {flight_plan}")
        
        try:
            response = self.http_session.put(
                f"{self.server_url}/api/flightplan",
                json=flight_plan,
                timeout=HTTP_TIMEOUT
            )
            response.raise_for_status()
            data = response.json()
            print(f"[DEBUG ATC] Flight plan registered successfully: {data.get('message', 'OK')}")
            print(f"[DEBUG ATC] Server response: {data}")
            return True
        except requests.exceptions.RequestException as e:
            print(f"[DEBUG ATC] ERROR registering flight plan: {e}")
            if hasattr(e, 'response') and e.response is not None:
                print(f"[DEBUG ATC]   Response status: {e.response.status_code}")
                try:
                    error_json = e.response.json()
                    print(f"[DEBUG ATC]   Response JSON: {error_json}")
                    error_detail = error_json.get('detail', 'Unknown error')
                    if e.response.status_code == 400:
                        print(f"[DEBUG ATC] Error (400 Bad Request): {error_detail}")
                        if 'invalid airport code' in error_detail.lower():
                            print("[DEBUG ATC]   One or more airport codes are invalid")
                    elif e.response.status_code == 422:
                        print(f"[DEBUG ATC] Error (422 Unprocessable Entity): {error_detail}")
                    else:
                        print(f"[DEBUG ATC] Error ({e.response.status_code}): {error_detail}")
                except:
                    print(f"[DEBUG ATC]   Response text: {e.response.text[:200]}")
            else:
                print(f"[DEBUG ATC]   No response object available")
            return False
    
    def send_loop(self) -> None:
        """Send GPS data to server via HTTP PUT at regular intervals."""
        print("[DEBUG ATC] Send loop started")
        loop_count = 0
        while self.running:
            try:
                current_time = time.time()
                loop_count += 1
                if loop_count == 1:
                    print(f"[DEBUG ATC] Send loop iteration #{loop_count} - ATC mode: {self.atc_mode_enabled}")
                
                # ATC Mode: Use fixed position instead of simulator data
                if self.atc_mode_enabled:
                    # Create fixed position GPS data object with all required fields
                    latest_gps = GPSData(
                        latitude=self.atc_latitude,
                        longitude=self.atc_longitude,
                        altitude=self.atc_altitude,
                        track=self.atc_heading,
                        magnetic_track=self.atc_heading,  # Use same as true heading (no magnetic declination for ATC)
                        indicated_air_speed=0.0,  # Not applicable for fixed position
                        vertical_speed=self.atc_vertical_speed,
                        ground_speed=self.atc_ground_speed,
                        raw_message=f"ATC_FIXED_POSITION:{self.atc_latitude},{self.atc_longitude}",  # Dummy raw message
                        source_type=DataSourceType.UDP  # Use UDP as source type (doesn't matter for ATC mode)
                    )
                    # Only print position every 10 iterations to reduce spam
                    if loop_count % 10 == 1 or loop_count <= 3:
                        print(f"[DEBUG ATC] Using fixed position: lat={self.atc_latitude:.6f}, lon={self.atc_longitude:.6f}, alt={self.atc_altitude:.1f}ft, hdg={self.atc_heading:.1f}°, spd={self.atc_ground_speed:.1f}kts")
                else:
                    # Get latest GPS data from data source
                    latest_gps = self.data_source.get_latest_gps()
                    
                    # If GPS data is stale, pause sending and optionally disconnect once
                    if latest_gps and not self.data_source.is_receiving_data(STALE_GPS_TIMEOUT):
                        if not self.stale_disconnect_sent:
                            # Send an explicit disconnect to the server
                            try:
                                self.http_session.delete(self.api_url, timeout=HTTP_TIMEOUT)
                            except requests.exceptions.RequestException:
                                # Ignore errors on disconnect attempt
                                pass
                            self.stale_disconnect_sent = True
                        # Stop sending until fresh data arrives
                        time.sleep(0.5)
                        continue

                # Check if we should send (time interval check)
                time_since_last_send = current_time - self.last_send_time
                should_send = latest_gps and time_since_last_send >= UPDATE_INTERVAL
                
                if loop_count <= 5:
                    print(f"[DEBUG ATC] Loop #{loop_count}: latest_gps={latest_gps is not None}, time_since_last={time_since_last_send:.2f}s, UPDATE_INTERVAL={UPDATE_INTERVAL}, should_send={should_send}")
                
                if should_send:
                    # Reset stale disconnect flag when we have fresh data
                    self.stale_disconnect_sent = False
                    
                    # Prepare JSON payload (callsign removed - it's sent via flight plan with highest precedence)
                    # API v2.2: Include squawk_code in position updates
                    payload = {
                        "latitude": latest_gps.latitude,
                        "longitude": latest_gps.longitude,
                        "altitude": latest_gps.altitude,
                        "track": latest_gps.track,
                        "ground_speed": latest_gps.ground_speed,
                        "vertical_speed": latest_gps.vertical_speed,  # Feet per minute (0.0 for UDP, actual value for FSWidget)
                        "timestamp": current_time,
                        "squawk_code": self.squawk_code  # Always send squawk code (defaults to "0000")
                    }
                    # NOTE: Callsign is NOT included here - it should only be sent via flight plan endpoint
                    # Flight plan callsign has highest precedence
                    
                    # DEBUG: Log position update being sent (every update, not just first few)
                    print(f"[DEBUG ATC] Sending position update #{loop_count}: lat={payload['latitude']:.6f}, lon={payload['longitude']:.6f}, alt={payload['altitude']:.1f}ft, track={payload['track']:.1f}°, speed={payload['ground_speed']:.1f}kts, squawk={payload['squawk_code']}")
                    
                    # Update aircraft status if we have airport information
                    if self.departure_airport_code and self.arrival_airport_code:
                        # Initialize aircraft status object if not already done
                        if self.aircraft_status_obj is None:
                            # Fetch airport data from API (for altitude) - this ensures state machine works out-of-the-box
                            dep_api_data = self.get_airport_info(self.departure_airport_code)
                            arr_api_data = self.get_airport_info(self.arrival_airport_code)
                            
                            # Get altitude from API (elevation_ft field), fallback to 0 if not found
                            dep_altitude = dep_api_data.get('elevation_ft', 0) if dep_api_data else 0
                            arr_altitude = arr_api_data.get('elevation_ft', 0) if arr_api_data else 0
                            
                            # Log if airport data was found or not
                            if not dep_api_data:
                                print(f"Warning: Airport {self.departure_airport_code} not found in server database, using default altitude 0")
                            else:
                                print(f"Airport {self.departure_airport_code} elevation: {dep_altitude} ft (from API)")
                            
                            if not arr_api_data:
                                print(f"Warning: Airport {self.arrival_airport_code} not found in server database, using default altitude 0")
                            else:
                                print(f"Airport {self.arrival_airport_code} elevation: {arr_altitude} ft (from API)")
                            
                            # Use empty lists for runways and gates (config file no longer used)
                            dep_runways = []
                            dep_gates = []
                            arr_runways = []
                            arr_gates = []
                            
                            # Create departure airport object with API altitude and config runways/gates
                            dep_airport = airport_status(
                                self.departure_airport_code,
                                dep_altitude,
                                dep_runways,
                                dep_gates
                            )
                            
                            # Create arrival airport object with API altitude and config runways/gates
                            arr_airport = airport_status(
                                self.arrival_airport_code,
                                arr_altitude,
                                arr_runways,
                                arr_gates
                            )
                            self.aircraft_status_obj = aircraft_status(
                                self.callsign or "UNKNOWN",
                                dep_airport,
                                arr_airport,
                                "",  # departure_time - not used yet
                                "",  # arrival_time - not used yet
                                self.callsign or "UNKNOWN",  # pilot
                                cruise_altitude=self.cruise_altitude,
                                action="",
                                gate="",
                                runway=""
                            )
                        
                        # Update aircraft status with current GPS data
                        if self.aircraft_status_obj:
                            #print(f"Aircraft status object: {self.aircraft_status_obj}")
                            # Ground speed is already in knots (normalized in GPSData)
                            # UDP: converted from m/s to knots during parsing
                            # FSWidget: already in knots, used as-is
                            if latest_gps.indicated_air_speed > 0:
                                # use the indicated air speed if it is available
                                speed_knots = latest_gps.indicated_air_speed
                            else:
                                speed_knots = latest_gps.ground_speed
                            self.aircraft_status_obj.set_speed(speed_knots)
                            #print(f"Setting altitude: {latest_gps.altitude}")
                            # why I do not get any other print statements after this?
                            self.aircraft_status_obj.set_altitude(latest_gps.altitude)
                            #print(f"Altitude set to: {latest_gps.altitude}")
                            #print(f"Altitude set to: {self.aircraft_status_obj.get_altitude()}")
                            # Update state based on current status
                            #print(f"Updating state based on current status")
                            # Run state machine ONCE - it both sets the state internally and returns the new state
                            # Note: aircraft_state_machine() calls set_state() internally, so we don't need to set it again
                            previous_state = self.aircraft_status_obj.get_state()
                            ar_status = self.aircraft_status_obj.aircraft_state_machine()
                            
                            # Check if state changed
                            #if ar_status != previous_state:
                                #print(f"🔄 State changed from '{previous_state}' to '{ar_status}'")
                            
                            # Update current status
                            self.current_aircraft_status = ar_status
                            
                            # Set departure time when transitioning to departing
                            if ar_status == "departing" and previous_state != "departing":
                                utc_time = datetime.now(datetime.UTC)
                                self.aircraft_status_obj.departure_time = utc_time.strftime("%Y-%m-%d %H:%M:%S")
                                #print(f"✈️ Departure time set: {self.aircraft_status_obj.departure_time}")
                            if ar_status == "landed" and previous_state != "landed":
                                utc_time = datetime.now(datetime.UTC)
                                self.aircraft_status_obj.arrival_time = utc_time.strftime("%Y-%m-%d %H:%M:%S")
                                #print(f"✈️ Arrival time set: {self.aircraft_status_obj.arrival_time}")
                            #print(f"Current aircraft status: {ar_status} (speed: {self.aircraft_status_obj.get_speed():.1f} kts, altitude: {self.aircraft_status_obj.get_altitude():.0f} ft)")
                            # Send Discord webhooks if enabled (webhook URL provided)
                            if self.discord_webhook_url and ar_status in ["departing", "landed", "taxiing"]:
                                #print(f"Sending Discord webhooks")
                                should_send = False
                                #print(f"Flag states: ar_status: {ar_status}, should_send: {should_send}, departure: {self.sent_departure_message}, arrival: {self.sent_arrival_message}, taxiing: {self.sent_taxiing_message}")
                                
                                # Check flags first, then create payload only if we need to send
                                
                                if ar_status == "departing" and not self.sent_departure_message:
                                    #print(f"Sending Discord webhook for departing")
                                    should_send = True
                                    self.sent_departure_message = True
                                    self.sent_arrival_message = False
                                    self.sent_taxiing_message = False
                                elif ar_status == "landed" and not self.sent_arrival_message:
                                    #print(f"Sending Discord webhook for landed")
                                    should_send = True
                                    self.sent_arrival_message = True
                                    self.sent_departure_message = False
                                    self.sent_taxiing_message = False
                                elif ar_status == "taxiing" and not self.sent_taxiing_message:
                                    #print(f"Sending Discord webhook for taxiing")
                                    should_send = True
                                    self.sent_taxiing_message = True
                                    self.sent_departure_message = False
                                    self.sent_arrival_message = False
                                
                                # Only create payload and send if we should send
                                if should_send:
                                    #print(f"Preparing Discord payload for {ar_status}")
                                    #print(f"  - departure_time: '{self.aircraft_status_obj.departure_time}'")
                                    #print(f"  - arrival_time: '{self.aircraft_status_obj.arrival_time}'")
                                    #print(f"  - gate: '{self.aircraft_status_obj.gate}'")
                                    #print(f"  - runway: '{self.aircraft_status_obj.runway}'")
                                    
                                    try:
                                        discord_payload = prepare_flight_data_for_discord(
                                                    self.aircraft_status_obj.aircraft_number,
                                                    self.aircraft_status_obj.get_departure_airport_name(),
                                                    self.aircraft_status_obj.get_arrival_airport_name(),
                                                    self.aircraft_status_obj.departure_time,
                                                    self.aircraft_status_obj.arrival_time,
                                                    self.aircraft_status_obj.pilot,
                                                    ar_status,
                                                    self.aircraft_status_obj.gate,
                                                    self.aircraft_status_obj.runway
                                                )
                                    except Exception as e:
                                        #print(f"Error preparing Discord payload: {e}")
                                        import traceback
                                        traceback.print_exc()
                                        discord_payload = None
                                    
                                    if discord_payload is None:
                                        #print(f"Error: Discord payload is None for action '{ar_status}'")
                                        pass
                                    else:
                                        try:
                                            #print(f"Sending Discord webhook for {ar_status}")
                                            #print(f"Discord payload: {discord_payload}")
                                            discord_response = requests.post(self.discord_webhook_url, json=discord_payload, verify=True, timeout=5.0)
                                            if discord_response.status_code != 200:
                                                print(f"Error sending Discord webhook: {ar_status} message: {discord_response.status_code} - {discord_response.text}")
                                                pass
                                            else:
                                                print(f"Successfully sent Discord webhook for {ar_status}")
                                        except Exception as e:
                                            print(f"Error sending Discord webhook: {e}")
                                            import traceback
                                            traceback.print_exc()
                                else:
                                    #print(f"Skipping Discord webhook for {ar_status} (already sent)")
                                    pass
                    else:
                        # No airport information, set status to unknown
                        print(f"No airport information, setting status to unknown")
                        self.current_aircraft_status = "unknown"
                    # Send HTTP PUT request
                    try:
                        print(f"[DEBUG ATC] PUT {self.api_url}")
                        response = self.http_session.put(
                            self.api_url,
                            json=payload,
                            timeout=HTTP_TIMEOUT
                        )
                        response.raise_for_status()  # Raise exception for bad status codes
                        
                        print(f"[DEBUG ATC] Position update sent successfully: status={response.status_code}")
                        if hasattr(response, 'text') and response.text:
                            try:
                                response_json = response.json()
                                print(f"[DEBUG ATC] Server response: {response_json}")
                            except:
                                print(f"[DEBUG ATC] Server response (text): {response.text[:200]}")
                        
                        self.messages_sent += 1
                        self.last_send_time = current_time
                        print(f"[DEBUG ATC] Updated last_send_time to {self.last_send_time:.2f}, next update in {UPDATE_INTERVAL}s")
                    
                    except requests.exceptions.RequestException as e:
                        self.http_errors += 1
                        if self.running:
                            print(f"[DEBUG ATC] ✗ HTTP error sending position: {e}")
                            if hasattr(e, 'response') and e.response is not None:
                                print(f"[DEBUG ATC]   Response: {e.response.status_code} - {e.response.text[:100]}")
                
                # Sleep a bit to avoid busy waiting
                time.sleep(0.1)
                
            except Exception as e:
                if self.running:
                    print(f"[DEBUG ATC] Error in send loop: {e}")
                    import traceback
                    traceback.print_exc()
                    time.sleep(1)  # Back off on error
    
    def start(self) -> None:
        """Start the client sender."""
        # Start simulator data source (only if not in ATC mode)
        if not self.atc_mode_enabled and self.data_source:
            self.data_source.start()
        elif self.atc_mode_enabled:
            print("[DEBUG ATC] Skipping data source start (ATC mode - using fixed position)")
        
        # Follow API v2.0 connection flow:
        # 1. Health Check (optional)
        try:
            test_response = self.http_session.get(
                f"{self.server_url}/api/health",
                timeout=HTTP_TIMEOUT
            )
            if test_response.status_code == 200:
                print(f"Server health check: OK")
        except requests.exceptions.RequestException as e:
            print(f"⚠ Warning: Could not reach server health endpoint: {e}")
            print("  Continuing anyway - will retry on first position update...")
        
        # 2. Register Flight Plan (before initial position)
        print("[DEBUG ATC] Registering flight plan...")
        flight_plan_success = self.register_flight_plan()
        if flight_plan_success:
            print("[DEBUG ATC] Flight plan registered successfully")
        else:
            print("[DEBUG ATC] ⚠ Warning: Flight plan registration failed, continuing anyway...")
        
        # 3. Initial Position (register client in system)
        # Send initial position with 0,0 to register client
        try:
            initial_payload = {
                "latitude": 0.0,
                "longitude": 0.0,
                "altitude": 0.0,
                "track": 0.0,
                "ground_speed": 0.0,
                "vertical_speed": 0.0,  # Feet per minute
                "timestamp": time.time(),
                "squawk_code": self.squawk_code  # API v2.2: Include squawk code in initial position
            }
            initial_response = self.http_session.put(
                self.api_url,
                json=initial_payload,
                timeout=HTTP_TIMEOUT
            )
            initial_response.raise_for_status()
            print("[DEBUG ATC] Initial position registered (0,0)")
            # Set last_send_time to allow immediate first real position update
            self.last_send_time = 0  # This ensures the first real update will be sent immediately
        except requests.exceptions.RequestException as e:
            print(f"[DEBUG ATC] ⚠ Warning: Could not register initial position: {e}")
            print("[DEBUG ATC]   Continuing anyway - will retry on first position update...")
            self.last_send_time = 0  # Still set to 0 to allow first update
        
        # 4. Start send thread (position updates)
        self.running = True
        self.send_thread = threading.Thread(target=self.send_loop, daemon=True)
        self.send_thread.start()
        print("[DEBUG ATC] Send thread started - position updates will be sent every 1 second")
        
        # Note: The blocking while True loop is removed for GUI usage
        # The GUI handles the main loop, and the send_thread runs in the background
    
    def stop(self) -> None:
        """Stop the client."""
        self.running = False
        
        # Stop simulator data source (only if it exists)
        if self.data_source:
            self.data_source.stop()
        else:
            print("[DEBUG ATC] No data source to stop (ATC mode)")
        
        if self.send_thread:
            self.send_thread.join(timeout=2)
        
        # Send explicit disconnect
        try:
            self.http_session.delete(self.api_url, timeout=HTTP_TIMEOUT)
            print("Sent disconnect to server")
        except requests.exceptions.RequestException:
            # Ignore errors on disconnect
            pass
        
        if self.http_session:
            self.http_session.close()
    
    def print_status(self) -> None:
        """Print current status."""
        uptime = time.time() - self.start_time
        success_rate = (self.messages_sent / (self.messages_sent + self.http_errors) * 100) if (self.messages_sent + self.http_errors) > 0 else 0
        
        latest_gps = self.data_source.get_latest_gps()
        if latest_gps:
            print(f"Last position: {latest_gps.latitude:.4f}, {latest_gps.longitude:.4f}")
        else:
            print("No GPS data received yet")
        
        print("=" * 60 + "\n")
    
    def get_aircraft_status(self) -> str:
        """Get the current aircraft status string."""
        return self.current_aircraft_status
    
    def update_flightplan_route(self, route: Optional[str]) -> None:
        """
        Update flight plan route text (API v2.2).
        
        Args:
            route: Raw route text from user input (will be sanitized when sent)
        """
        self.flightplan_route = route
    
    def update_squawk_code(self, squawk: Optional[str]) -> None:
        """
        Update squawk code (API v2.2).
        
        Args:
            squawk: Raw squawk code from user input (will be validated and padded)
        """
        self.squawk_code = self.validate_and_pad_squawk_code(squawk)


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description="Abflug Client - Forward Aerofly data to multiplayer server (API v2.0)"
    )
    parser.add_argument(
        '--api-key',
        required=False,
        default=None,
        help='Your API key for authentication (will try butter.json if not provided)'
    )
    parser.add_argument(
        '--server',
        required=False,
        default=None,
        help='Server URL (e.g., https://abflug.cloud, http://abflug.cloud, or your-server.com). Will use config file if not provided.'
    )
    parser.add_argument(
        '--use-https',
        action='store_true',
        help='Use HTTPS instead of HTTP (default: HTTP)'
    )
    parser.add_argument(
        '--aerofly-port',
        type=int,
        default=DEFAULT_AEROFLY_PORT,
        help=f'Port to receive Aerofly data (default: {DEFAULT_AEROFLY_PORT})'
    )
    parser.add_argument(
        '--callsign',
        type=str,
        default=None,
        help='Your callsign (optional, sent via flight plan with highest precedence)'
    )
    parser.add_argument(
        '--use-fswidget',
        action='store_true',
        help='Use FSWidget TCP protocol instead of Aerofly UDP (default: UDP)'
    )
    parser.add_argument(
        '--fswidget-ip',
        type=str,
        default='localhost',
        help='IP address for FSWidget TCP connection (default: localhost)'
    )
    parser.add_argument(
        '--send-to-discord',
        type=str,
        default=None,
        help='Discord webhook URL for status updates (optional). Example: https://discord.com/api/webhooks/...'
    )
    parser.add_argument(
        '--departure-airport',
        type=str,
        default=None,
        help='Departure airport ICAO code (optional, e.g., LOWG)'
    )
    parser.add_argument(
        '--arrival-airport',
        type=str,
        default=None,
        help='Arrival airport ICAO code (optional, e.g., EDDF)'
    )
    parser.add_argument(
        '--airplane-model',
        type=str,
        default=None,
        help='Aircraft model (optional, e.g., Boeing 737-800)'
    )
    parser.add_argument(
        '--airplane-livery',
        type=str,
        default=None,
        help='Aircraft livery/airline (optional, e.g., United Airlines)'
    )
    parser.add_argument(
        '--cruise-altitude',
        type=str,
        default=None,
        help='Cruise altitude in feet or FL notation (optional, e.g., 30000, FL300, FL270). Default: 30000'
    )
    parser.add_argument(
        '--auto-detect',
        action='store_true',
        help='Automatically detect simulator IP address on local network (works for both UDP and FSWidget)'
    )
    parser.add_argument(
        '--flightplan-route',
        type=str,
        default=None,
        help='Flight plan route text (max 500 chars, alphanumeric + spaces only, optional). Example: "KSFO SID ROUTE STAR KLAX"'
    )
    parser.add_argument(
        '--squawk-code',
        type=str,
        default=None,
        help='Squawk code (4 digits, 0000-9999, optional). Defaults to "0000" if not provided or invalid.'
    )
    args = parser.parse_args()
    
    # Get API key from command line or config file
    api_key = args.api_key
    if not api_key:
        api_key = get_butter_api_key()
        if api_key:
            print("Using API key from config file")
        else:
            print("Error: No API key provided and config file not found or has no API key")
            print("Please provide --api-key or create butter.json/abflug.json with an 'api_key' field")
            return 1
    else:
        api_key = api_key.strip()
        if not api_key:
            print("Error: Empty API key provided")
            return 1
    
    # Get server URL from command line or config file
    server_url = args.server
    if not server_url:
        server_url = get_server_url()
        print(f"Using server URL from config file: {server_url}")
    else:
        server_url = server_url.strip()
        if not server_url:
            print("Error: Empty server URL provided")
            return 1
    
    # Parse cruise altitude (handle FL notation)
    cruise_altitude = None
    if args.cruise_altitude:
        cruise_alt_str = args.cruise_altitude.strip().upper()
        if cruise_alt_str.startswith("FL"):
            # FL notation: FL300 = 30000 ft, FL270 = 27000 ft, FL15 = 1500 ft
            try:
                fl_number = int(cruise_alt_str[2:])
                cruise_altitude = fl_number * 100
            except ValueError:
                print(f"Error: Invalid FL notation '{args.cruise_altitude}'. Use format FL### (e.g., FL300)")
                return 1
        else:
            # Direct feet value
            try:
                cruise_altitude = int(cruise_alt_str)
            except ValueError:
                print(f"Error: Invalid cruise altitude '{args.cruise_altitude}'. Must be a number or FL notation (e.g., 30000 or FL300)")
                return 1
    # Validate Discord webhook URL if provided
    if args.send_to_discord:
        if not args.send_to_discord.strip():
            args.send_to_discord = None
        elif not args.send_to_discord.startswith("https://discord.com/api/webhooks/"):
            print("Error: Invalid Discord webhook URL (must start with https://discord.com/api/webhooks/)")
            return 1
    
    # Validate and pad squawk code if provided (API v2.2)
    squawk_code = None
    if args.squawk_code:
        squawk_code = AbflugClient.validate_and_pad_squawk_code(args.squawk_code)
        if squawk_code == "0000" and args.squawk_code.strip() != "0000":
            print(f"Warning: Invalid squawk code '{args.squawk_code}', defaulting to '0000'")
    
    # Sanitize flightplan route if provided (API v2.2)
    flightplan_route = None
    if args.flightplan_route:
        flightplan_route = AbflugClient.sanitize_flightplan_route(args.flightplan_route)
        if flightplan_route == "NO DATA" and args.flightplan_route.strip():
            print(f"Warning: Flight plan route sanitization resulted in 'NO DATA', original was: '{args.flightplan_route[:100]}...'")
    
    try:
        client = AbflugClient(
            api_key=api_key,
            server_url=server_url,
            aerofly_port=args.aerofly_port,
            use_https=args.use_https,
            callsign=args.callsign,
            use_fswidget=args.use_fswidget,
            fswidget_ip=args.fswidget_ip,
            send_to_discord=args.send_to_discord,
            departure_airport=args.departure_airport,
            arrival_airport=args.arrival_airport,
            airplane_model=args.airplane_model,
            airplane_livery=args.airplane_livery,
            cruise_altitude=cruise_altitude,
            auto_detect=args.auto_detect,
            flightplan_route=flightplan_route,
            squawk_code=squawk_code
        )
        client.start()
    except Exception as e:
        print(f"Fatal error: {e}")
        import traceback
        traceback.print_exc()
        return 1
    
    return 0


if __name__ == '__main__':
    exit(main())

