"""Service for PX4 health and calibration snapshot."""

from __future__ import annotations

import asyncio
from typing import Optional, Dict, Any

from mavsdk import System

from ..config import settings
from ..models import Px4HealthResponse, CalibrationHealth


async def _get_health_once(drone: System, timeout_s: float = 5.0):
    """
    Wait for the first telemetry.health() message.

    Raises asyncio.TimeoutError if nothing is received within timeout_s.
    """
    async def _first():
        async for health in drone.telemetry.health():
            return health

    return await asyncio.wait_for(_first(), timeout_s)


async def _get_gps_info_once(drone: System, timeout_s: float = 5.0):
    """
    Wait for the first telemetry.gps_info() message.

    Raises asyncio.TimeoutError if nothing is received within timeout_s.
    """
    async def _first():
        async for gps_info in drone.telemetry.gps_info():
            return gps_info

    return await asyncio.wait_for(_first(), timeout_s)


async def read_px4_health(
    port: Optional[str] = None,
    baudrate: Optional[int] = None,
    timeout_s: float = 10.0,
) -> Px4HealthResponse:
    """
    Connect to PX4 via MAVSDK and return a one-shot health snapshot.

    - Uses telemetry.health() for calibration + armable flags.
    - Uses telemetry.gps_info() for fix type + satellites.
    - Returns Px4HealthResponse; never raises to the API layer.
    """

    # Fallback to global settings if not provided explicitly
    port = port or settings.default_port
    baudrate = baudrate or settings.default_baudrate
    serial = f"serial://{port}:{baudrate}"

    drone = System()

    try:
        # Connect to PX4
        await drone.connect(system_address=serial)

        # Get health & GPS within the global timeout
        health = await _get_health_once(drone, timeout_s=timeout_s / 2)
        try:
            gps_info = await _get_gps_info_once(drone, timeout_s=timeout_s / 2)
        except asyncio.TimeoutError:
            gps_info = None

        # Build calibration health
        calibration = CalibrationHealth(
            gyroscope_ok=health.is_gyrometer_calibration_ok,
            accelerometer_ok=health.is_accelerometer_calibration_ok,
            magnetometer_ok=health.is_magnetometer_calibration_ok,
        )

        # GPS fields
        gps_fix_type: Optional[str] = None
        satellites_used: Optional[int] = None

        if gps_info is not None:
            # gps_info.fix_type is an enum; prefer its name
            if hasattr(gps_info.fix_type, "name"):
                gps_fix_type = gps_info.fix_type.name.lower()
            else:
                gps_fix_type = str(gps_info.fix_type).lower()

            # MAVSDK docs: num_satellites = number of visible satellites in use
            satellites_used = int(getattr(gps_info, "num_satellites", 0))

        # High-level status heuristic
        if calibration.requires_calibration:
            status = "degraded"
        else:
            status = "ok"

        # Raw health flags (optional, but handy for debugging / advanced UI)
        raw_health: Dict[str, Any] = {
            "is_gyrometer_calibration_ok": health.is_gyrometer_calibration_ok,
            "is_accelerometer_calibration_ok": health.is_accelerometer_calibration_ok,
            "is_magnetometer_calibration_ok": health.is_magnetometer_calibration_ok,
            "is_local_position_ok": getattr(health, "is_local_position_ok", None),
            "is_global_position_ok": getattr(health, "is_global_position_ok", None),
            "is_home_position_ok": getattr(health, "is_home_position_ok", None),
            "is_armable": health.is_armable,
        }

        return Px4HealthResponse(
            status=status,
            connection_port=port,
            connection_baudrate=baudrate,
            calibration=calibration,
            gps_fix_type=gps_fix_type,
            satellites_used=satellites_used,
            armable=health.is_armable,
            raw_health=raw_health,
        )

    except (asyncio.TimeoutError, Exception):
        # On any connection / telemetry failure, report "disconnected"
        return Px4HealthResponse(
            status="disconnected",
            connection_port=port,
            connection_baudrate=baudrate,
            calibration=None,
            gps_fix_type=None,
            satellites_used=None,
            armable=False,
            raw_health=None,
        )
