"""Pydantic data models for LC29H GNSS data."""

from datetime import datetime
from enum import IntEnum

from pydantic import BaseModel, Field, computed_field

from .geo import ecef_to_wgs84


class FixType(IntEnum):
    """GNSS fix type from GGA quality indicator."""

    NO_FIX = 0
    GPS_FIX = 1
    DGPS_FIX = 2
    PPS_FIX = 3
    RTK_FIXED = 4
    RTK_FLOAT = 5
    ESTIMATED = 6
    MANUAL = 7
    SIMULATION = 8


class Constellation(IntEnum):
    """GNSS constellation identifiers."""

    GPS = 0
    GLONASS = 1
    GALILEO = 2
    BEIDOU = 3
    QZSS = 4


class Position(BaseModel):
    """Current GNSS position fix."""

    latitude: float = Field(default=0.0, description="Latitude in decimal degrees (+ = N, - = S)")
    longitude: float = Field(default=0.0, description="Longitude in decimal degrees (+ = E, - = W)")
    altitude: float = Field(default=0.0, description="Altitude above mean sea level in meters")
    fix_type: FixType = Field(default=FixType.NO_FIX, description="GNSS fix quality")
    hdop: float = Field(default=99.99, description="Horizontal dilution of precision")
    satellites: int = Field(default=0, description="Number of satellites used in fix")
    timestamp: datetime | None = Field(default=None, description="UTC time of fix")
    speed_knots: float = Field(default=0.0, description="Speed over ground in knots")
    course: float = Field(default=0.0, description="Course over ground in degrees true")


class SatelliteInfo(BaseModel):
    """Information about a single tracked satellite."""

    prn: int = Field(description="Satellite PRN number")
    elevation: int = Field(default=0, description="Elevation angle in degrees (0-90)")
    azimuth: int = Field(default=0, description="Azimuth in degrees from true north (0-359)")
    snr: int | None = Field(default=None, description="Signal-to-noise ratio in dB-Hz")
    constellation: Constellation = Field(
        default=Constellation.GPS, description="GNSS constellation"
    )
    used_in_fix: bool = Field(default=False, description="Whether satellite is used in fix")
    signal_id: int = Field(default=0, description="NMEA signal ID (0=unknown, 1-8=band-specific)")
    band: str = Field(default="", description="Signal band name (e.g. L1, L5, B1I, B2a)")


class ModuleInfo(BaseModel):
    """Module identification and firmware info."""

    firmware_version: str = Field(default="", description="Firmware version string")
    model: str = Field(default="", description="Module model (e.g. LC29HDA)")
    build_date: str = Field(default="", description="Firmware build date")
    build_time: str = Field(default="", description="Firmware build time")
    chip_version: str = Field(default="", description="Chip firmware (e.g. AG3335A_V2.6.0)")
    chip_model: str = Field(default="", description="Chip model (e.g. AG3335A)")


class NtripConfig(BaseModel):
    """NTRIP caster connection configuration."""

    host: str = Field(description="NTRIP caster hostname or IP")
    port: int = Field(default=2101, description="NTRIP caster port")
    mountpoint: str = Field(default="", description="NTRIP mountpoint name")
    username: str = Field(default="", description="NTRIP username")
    password: str = Field(default="", description="NTRIP password")


class NtripMountPoint(BaseModel):
    """NTRIP source table mount point entry."""

    name: str = Field(description="Mountpoint name")
    identifier: str = Field(default="", description="Source identifier")
    format: str = Field(default="", description="Data format (e.g. RTCM 3.2)")
    format_details: str = Field(default="", description="Format details (e.g. 1005(1),1074(1),...)")
    carrier: int = Field(default=0, description="Carrier phase: 0=no, 1=L1, 2=L1+L2")
    nav_system: str = Field(default="", description="Navigation system (e.g. GPS+GLO+GAL+BDS)")
    network: str = Field(default="", description="Network name")
    country: str = Field(default="", description="Country code")
    latitude: float = Field(default=0.0, description="Approximate latitude")
    longitude: float = Field(default=0.0, description="Approximate longitude")
    nmea_required: bool = Field(default=False, description="Whether GGA is required")
    solution: str = Field(default="", description="Solution type")
    generator: str = Field(default="", description="Generator software")
    compression: str = Field(default="", description="Compression algorithm")
    authentication: str = Field(default="N", description="Auth: N=none, B=basic, D=digest")
    fee: str = Field(default="N", description="Fee: Y=yes, N=no")
    bitrate: int = Field(default=0, description="Bitrate in bps")


class SurveyInStatus(BaseModel):
    """Base station survey-in progress."""

    active: bool = Field(default=False, description="Whether survey-in is in progress")
    valid: bool = Field(default=False, description="Whether result is valid")
    duration: int = Field(default=0, description="Elapsed duration in seconds")
    mean_x: float = Field(default=0.0, description="Mean ECEF X in meters")
    mean_y: float = Field(default=0.0, description="Mean ECEF Y in meters")
    mean_z: float = Field(default=0.0, description="Mean ECEF Z in meters")
    mean_accuracy: float = Field(default=0.0, description="Mean 3D accuracy in meters")

    @computed_field(description="Latitude in decimal degrees (from ECEF conversion)")
    @property
    def latitude(self) -> float | None:
        if self.mean_x == 0.0 and self.mean_y == 0.0 and self.mean_z == 0.0:
            return None
        lat, _, _ = ecef_to_wgs84(self.mean_x, self.mean_y, self.mean_z)
        return lat

    @computed_field(description="Longitude in decimal degrees (from ECEF conversion)")
    @property
    def longitude(self) -> float | None:
        if self.mean_x == 0.0 and self.mean_y == 0.0 and self.mean_z == 0.0:
            return None
        _, lon, _ = ecef_to_wgs84(self.mean_x, self.mean_y, self.mean_z)
        return lon

    @computed_field(description="Altitude above WGS84 ellipsoid in meters (from ECEF conversion)")
    @property
    def altitude_m(self) -> float | None:
        if self.mean_x == 0.0 and self.mean_y == 0.0 and self.mean_z == 0.0:
            return None
        _, _, alt = ecef_to_wgs84(self.mean_x, self.mean_y, self.mean_z)
        return alt
