from __future__ import annotations

from enum import Enum
from typing import Literal, Optional, Dict, Any, List

from pydantic import BaseModel, Field, ConfigDict


class CalibrationType(Enum):
    """Supported calibration types."""
    GYROSCOPE = "gyroscope"
    ACCELEROMETER = "accelerometer"
    MAGNETOMETER = "magnetometer"
    HORIZON = "horizon"
    ESC = "esc"


class CalibrationRequest(BaseModel):
    """Request model for calibration operations.

    Exactly one calibration type is selected via `calibration_type`.
    """

    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "calibration_type": "accelerometer",
                "port": "/dev/pixhawk",
                "baudrate": 2_000_000,
            }
        }
    )

    calibration_type: CalibrationType = Field(
        description=(
            "Which calibration to run. "
            "One of: gyroscope, accelerometer, magnetometer, horizon, esc."
        )
    )
    port: str = Field(
        default="/dev/pixhawk",
        description="Serial port device.",
    )
    baudrate: int = Field(
        default=2_000_000,
        description="Serial baudrate (must be positive).",
        ge=1,
    )

class CalibrationHealth(BaseModel):
    """Calibration health response."""

    gyroscope_ok: bool = Field(..., description="Whether the gyroscope is calibrated.")
    accelerometer_ok: bool = Field(..., description="Whether the accelerometer is calibrated.")
    magnetometer_ok: bool = Field(..., description="Whether the magnetometer is calibrated.")

    @property
    def requires_calibration(self) -> bool:
        """Whether the calibration is required."""
        return not (self.gyroscope_ok and self.accelerometer_ok and self.magnetometer_ok)


class HealthAndArmingCheckProblem(BaseModel):
    """A single health/arming check problem from PX4 Events."""

    message: str = Field(..., description="Short, single-line message.")
    description: str = Field(default="", description="Detailed description (optional).")
    health_component: str = Field(default="", description="Associated component, e.g. 'gps'.")


class HealthAndArmingCheckMode(BaseModel):
    """Arming checks for a specific flight mode (from PX4 Events)."""

    mode_name: str = Field(..., description="Mode name, e.g. 'Position'.")
    can_arm_or_run: bool = Field(
        ...,
        description="If disarmed: whether arming is possible. If armed: whether the mode can be selected.",
    )
    problems: List[HealthAndArmingCheckProblem] = Field(
        default_factory=list,
        description="List of reported problems for this mode.",
    )


class HealthComponentReport(BaseModel):
    """Health component report (e.g. GPS, Accelerometer) from PX4 Events."""

    name: str = Field(..., description="Unique component name, e.g. 'gps'.")
    label: str = Field(..., description="Human-readable label, e.g. 'GPS'.")
    is_present: bool = Field(..., description="Whether the component is present.")
    has_error: bool = Field(..., description="Whether the component has errors.")
    has_warning: bool = Field(..., description="Whether the component has warnings.")


class HealthAndArmingCheckReport(BaseModel):
    """
    Health and arming checks report from MAVSDK Events plugin.

    Provides detailed readiness-to-fly information: current mode arming status,
    per-component health, and all reported problems.
    """

    current_mode_intention: Optional[HealthAndArmingCheckMode] = Field(
        default=None,
        description="Report for the currently intended flight mode (can_arm_or_run, problems).",
    )
    health_components: List[HealthComponentReport] = Field(
        default_factory=list,
        description="Health components (e.g. gps, accelerometer) with present/error/warning flags.",
    )
    all_problems: List[HealthAndArmingCheckProblem] = Field(
        default_factory=list,
        description="Complete list of all reported problems.",
    )


class BatteryStatus(BaseModel):
    """Simplified battery status snapshot."""

    voltage_v: Optional[float] = Field(
        default=None,
        description="Battery voltage in volts, if available.",
    )
    remaining_percent: Optional[float] = Field(
        default=None,
        ge=0,
        le=100,
        description="Estimated remaining charge in percent (0–100), if available.",
    )
    current_a: Optional[float] = Field(
        default=None,
        description="Battery current draw in amperes, if available.",
    )


class Px4HealthResponse(BaseModel):
    """
    High-level PX4 health snapshot.

    Combines:
    - overall status (ok / degraded / disconnected)
    - connection information
    - calibration health (if available)
    - GPS fix/used satellites
    - armable flag
    - optional raw health flags for debugging/advanced UI
    """

    status: Literal["ok", "degraded", "disconnected"] = Field(
        ...,
        description="High-level PX4 health status derived from telemetry.",
    )
    connection_port: str = Field(
        ...,
        description="Serial port used to reach the PX4 autopilot.",
    )
    connection_baudrate: int = Field(
        ...,
        description="Baudrate used on the serial link to PX4.",
    )

    calibration: Optional[CalibrationHealth] = Field(
        default=None,
        description="Sensor calibration health, if telemetry health was received.",
    )

    gps_fix_type: Optional[str] = Field(
        default=None,
        description=(
            "GPS fix type as a lowercase string (e.g. 'no_fix', '2d_fix', '3d_fix', "
            "'rtk_float', 'rtk_fix'), if GPS info is available."
        ),
    )
    satellites_used: Optional[int] = Field(
        default=None,
        ge=0,
        description=(
            "Number of satellites currently used for the positioning solution, "
            "if GPS info is available."
        ),
    )

    armable: Optional[bool] = Field(
        default=None,
        description="True if PX4 considers the vehicle armable.",
    )

    arming_checks_report: Optional[HealthAndArmingCheckReport] = Field(
        default=None,
        description=(
            "Detailed health and arming checks from MAVSDK Events plugin "
            "(mode intention, health components, all problems). None if Events plugin unavailable."
        ),
    )

    battery: Optional[BatteryStatus] = Field(
        default=None,
        description="Battery status snapshot derived from MAVSDK telemetry.battery(), if available.",
    )

    raw_health: Optional[Dict[str, Any]] = Field(
        default=None,
        description=(
            "Optional low-level health flags directly from MAVSDK telemetry.health(), "
            "for debugging or advanced UI use."
        ),
    )


class Px4InfoResponse(BaseModel):
    """
    Basic PX4 product / firmware information.

    Values are derived from MAVSDK's Info plugin:
    - product_name from Info.get_product()
    - firmware_version from Info.get_version()
    """

    product_name: Optional[str] = Field(
        default=None,
        description="Human-readable PX4 flight controller product name, if available.",
    )
    vendor_name: Optional[str] = Field(
        default=None,
        description="Board vendor name, if available.",
    )
    firmware_version: Optional[str] = Field(
        default=None,
        description=(
            "PX4 flight stack version in the form 'major.minor.patch', "
            "if version information is available."
        ),
    )
