import json
import logging
import time
import math
import requests
from pathlib import Path
from dataclasses import dataclass
from typing import Optional, Dict, Any, List

from rich.live import Live
from rich.table import Table
from rich.panel import Panel
from rich.layout import Layout
from rich import box

# Configuration du logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

CONFIG_FILE = Path(__file__).parent / "config.json"

@dataclass
class ServerStatus:
    dump1090_status: str
    piaware_status: str
    total_messages_received: int
    aircraft_tracked: int
    cpu_temp: Optional[float] = None
    system_load: Optional[float] = None
    memory_usage: Optional[float] = None

@dataclass
class AircraftData:
    hex_id: str
    callsign: str
    latitude: Optional[float]
    longitude: Optional[float]
    altitude: Optional[int]
    heading: Optional[float]
    ground_speed: Optional[float]
    vertical_rate: Optional[int]
    rssi: Optional[float]
    seen: float
    category: str = "unknown"
    squawk: Optional[str] = None
    distance: Optional[float] = None
    bearing: Optional[float] = None

def haversine(lat1, lon1, lat2, lon2):
    """Calcule la distance en km entre deux points GPS."""
    if None in (lat1, lon1, lat2, lon2):
        return None
    R = 6371.0 # Rayon de la terre en km
    lat1_rad, lon1_rad = math.radians(lat1), math.radians(lon1)
    lat2_rad, lon2_rad = math.radians(lat2), math.radians(lon2)
    
    dlat = lat2_rad - lat1_rad
    dlon = lon2_rad - lon1_rad
    
    a = math.sin(dlat / 2)**2 + math.cos(lat1_rad) * math.cos(lat2_rad) * math.sin(dlon / 2)**2
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    return R * c

def calculate_bearing(lat1, lon1, lat2, lon2):
    lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
    dlon = lon2 - lon1
    x = math.sin(dlon) * math.cos(lat2)
    y = math.cos(lat1) * math.sin(lat2) - (math.sin(lat1) * math.cos(lat2) * math.cos(dlon))
    return (math.degrees(math.atan2(x, y)) + 360) % 360

class PiAwareClient:
    def __init__(self, ip_address: str, timeout: int = 2):
        self.ip_address = ip_address
        self.timeout = timeout
        self.base_url = f"http://{ip_address}"

    def fetch_status(self) -> Optional[Dict[str, Any]]:
        url = f"{self.base_url}/status.json"
        try:
            response = requests.get(url, timeout=self.timeout)
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            logger.debug(f"Impossible de récupérer /status.json : {e}")
            return None

    def fetch_aircraft(self) -> Optional[Dict[str, Any]]:
        url = f"{self.base_url}/skyaware/data/aircraft.json"
        try:
            response = requests.get(url, timeout=self.timeout)
            if response.status_code == 404:
                url = f"{self.base_url}/dump1090-fa/data/aircraft.json"
                response = requests.get(url, timeout=self.timeout)
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            logger.error(f"Erreur de connexion pour les avions (IP: {self.ip_address}): {e}")
            return None

    def parse_status(self, raw_data: Dict[str, Any], total_messages: int, total_aircraft: int) -> ServerStatus:
        return ServerStatus(
            dump1090_status="Running",
            piaware_status=raw_data.get("piaware", {}).get("status", "unknown"),
            total_messages_received=total_messages,
            aircraft_tracked=total_aircraft,
            cpu_temp=raw_data.get("cpu_temp_celcius"),
            system_load=raw_data.get("cpu_load_percent"),
            memory_usage=None
        )

    def parse_aircraft(self, raw_aircraft: List[Dict[str, Any]], receiver_lat: float = None, receiver_lon: float = None) -> List[AircraftData]:
        parsed = []
        for a in raw_aircraft:
            # Ignorer les avions qui n'ont pas été vus depuis plus de 30 secondes
            if a.get("seen", 0) > 30:
                continue
                
            lat = a.get("lat")
            lon = a.get("lon")
            distance = haversine(receiver_lat, receiver_lon, lat, lon) if (receiver_lat and receiver_lon) else None
            bearing = calculate_bearing(receiver_lat, receiver_lon, lat, lon) if (distance is not None) else None
                
            parsed.append(AircraftData(
                hex_id=a.get("hex", "unknown"),
                callsign=a.get("flight", "").strip() or "N/A",
                latitude=lat,
                longitude=lon,
                altitude=a.get("alt_baro") if a.get("alt_baro") is not None else a.get("alt_geom"),
                heading=a.get("track") if a.get("track") is not None else a.get("mag_heading"),
                ground_speed=a.get("gs"),
                vertical_rate=a.get("baro_rate") or a.get("geom_rate"),
                rssi=a.get("rssi"),
                seen=a.get("seen", 0),
                category=a.get("category", "unknown"),
                squawk=str(a.get("squawk")).zfill(4) if a.get("squawk") is not None else None,
                distance=distance,
                bearing=bearing
            ))
        return parsed

def load_config():
    """Charge la configuration depuis config.json."""
    if not CONFIG_FILE.exists():
        logger.error(f"Le fichier de configuration {CONFIG_FILE.name} est introuvable.")
        return None
    
    try:
        with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
            config = json.load(f)
            return config
    except json.JSONDecodeError:
        logger.error("Le fichier config.json n'est pas un JSON valide.")
        return None
    except Exception as e:
        logger.error(f"Erreur lors de la lecture de la configuration : {e}")
        return None

def generate_ui(status: Optional[ServerStatus], aircraft_list: List[AircraftData]) -> Layout:
    layout = Layout()
    layout.split(
        Layout(name="header", size=4),
        Layout(name="body")
    )

    # Header Panel
    if status:
        header_text = (
            f"[bold cyan]PiAware Status:[/bold cyan] {status.piaware_status.upper()} | "
            f"[bold cyan]Temp:[/bold cyan] {status.cpu_temp}°C | "
            f"[bold cyan]Load:[/bold cyan] {status.system_load}% | "
            f"[bold cyan]Messages:[/bold cyan] {status.total_messages_received} | "
            f"[bold cyan]Avions Suivis:[/bold cyan] {status.aircraft_tracked}"
        )
    else:
        header_text = "[red]Données de statut indisponibles ou en attente...[/red]"

    layout["header"].update(Panel(header_text, title="Moniteur PiAware", style="white on dark_blue"))

    # Table d'avions
    table = Table(box=box.SIMPLE, expand=True)
    table.add_column("HEX", style="cyan")
    table.add_column("Callsign", style="magenta")
    table.add_column("Alt (ft)", justify="right", style="green")
    table.add_column("Speed (kt)", justify="right", style="yellow")
    table.add_column("Heading (°)", justify="right")
    table.add_column("V.Rate", justify="right")
    table.add_column("RSSI", justify="right")
    table.add_column("Seen (s)", justify="right")

    for a in sorted(aircraft_list, key=lambda x: x.seen):
        table.add_row(
            a.hex_id,
            a.callsign,
            str(a.altitude) if a.altitude is not None else "-",
            str(a.ground_speed) if a.ground_speed is not None else "-",
            str(a.heading) if a.heading is not None else "-",
            str(a.vertical_rate) if a.vertical_rate is not None else "-",
            f"{a.rssi:.1f}" if a.rssi is not None else "-",
            f"{a.seen:.1f}"
        )

    layout["body"].update(Panel(table, title=f"Vols en Temps Réel ({len(aircraft_list)})"))
    return layout

def main():
    # Désactiver le logging dans la console pour ne pas interférer avec Rich Live
    logger.setLevel(logging.CRITICAL)
    
    config = load_config()
    if not config:
        print("Arrêt du script : configuration manquante ou invalide.")
        return
    
    piaware_ip = config.get("PIAWARE_IP")
    refresh_rate = config.get("REFRESH_RATE", 2)
    
    if not piaware_ip:
        print("Arrêt du script : PIAWARE_IP n'est pas défini dans le fichier de configuration.")
        return
        
    client = PiAwareClient(ip_address=piaware_ip)
    
    with Live(generate_ui(None, []), refresh_per_second=1, screen=True) as live:
        try:
            while True:
                raw_aircraft_data = client.fetch_aircraft()
                raw_status_data = client.fetch_status()
                
                aircraft_list = []
                total_messages = 0
                
                if raw_aircraft_data:
                    total_messages = raw_aircraft_data.get("messages", 0)
                    aircraft_list = client.parse_aircraft(raw_aircraft_data.get("aircraft", []))
                
                status_obj = None
                if raw_status_data:
                    status_obj = client.parse_status(raw_status_data, total_messages, len(aircraft_list))
                
                live.update(generate_ui(status_obj, aircraft_list))
                time.sleep(refresh_rate)
        except KeyboardInterrupt:
            pass

if __name__ == "__main__":
    main()
