"""Convert NMEA sentences to TelemetryPoint."""

from datetime import datetime, timezone
from uuid import UUID

from fishinfo.types import DataType, TelemetryPoint

from .ais import AISMessage, AISPositionReport, AISStaticData
from .sentences import (
    DBTSentence,
    DPTSentence,
    GGASentence,
    HDGSentence,
    HDTSentence,
    MTWSentence,
    MWVSentence,
    NMEASentence,
    RMCSentence,
    VTGSentence,
)


class NMEAConverter:
    """
    Convert parsed NMEA sentences to TelemetryPoint objects.

    Maps sentence types to appropriate DataType enum values and
    extracts relevant fields for telemetry ingestion.
    """

    def __init__(
        self,
        vessel_id: UUID | str,
        tenant_id: UUID | str,
        source: str = "nmea_0183",
    ) -> None:
        """
        Initialize NMEA converter.

        Args:
            vessel_id: UUID of the vessel
            tenant_id: UUID of the tenant
            source: Source identifier for telemetry points
        """
        self.vessel_id = vessel_id if isinstance(vessel_id, UUID) else UUID(vessel_id)
        self.tenant_id = tenant_id if isinstance(tenant_id, UUID) else UUID(tenant_id)
        self.source = source

    def to_telemetry_point(
        self,
        sentence: NMEASentence | AISMessage,
        timestamp: datetime | None = None,
    ) -> TelemetryPoint | list[TelemetryPoint]:
        """
        Convert NMEA sentence or AIS message to TelemetryPoint(s).

        Some sentences may generate multiple telemetry points (e.g., RMC
        contains both position and speed data).

        Args:
            sentence: Parsed NMEA sentence or AIS message
            timestamp: Override timestamp (uses sentence time if None)

        Returns:
            Single TelemetryPoint or list of TelemetryPoints

        Examples:
            >>> converter = NMEAConverter(vessel_id=UUID("..."), tenant_id=UUID("..."))
            >>> gga = parser.parse("$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47")
            >>> point = converter.to_telemetry_point(gga)
            >>> point.data_type
            <DataType.POSITION: 'position'>
        """
        # Determine timestamp
        time = timestamp or getattr(sentence, "time", None) or datetime.now(timezone.utc)

        # Convert based on sentence type
        if isinstance(sentence, GGASentence):
            return self._convert_gga(sentence, time)
        elif isinstance(sentence, RMCSentence):
            return self._convert_rmc(sentence, time)
        elif isinstance(sentence, VTGSentence):
            return self._convert_vtg(sentence, time)
        elif isinstance(sentence, HDTSentence):
            return self._convert_hdt(sentence, time)
        elif isinstance(sentence, HDGSentence):
            return self._convert_hdg(sentence, time)
        elif isinstance(sentence, DBTSentence):
            return self._convert_dbt(sentence, time)
        elif isinstance(sentence, DPTSentence):
            return self._convert_dpt(sentence, time)
        elif isinstance(sentence, MWVSentence):
            return self._convert_mwv(sentence, time)
        elif isinstance(sentence, MTWSentence):
            return self._convert_mtw(sentence, time)
        elif isinstance(sentence, AISPositionReport):
            return self._convert_ais_position(sentence, time)
        elif isinstance(sentence, AISStaticData):
            return self._convert_ais_static(sentence, time)
        else:
            raise ValueError(f"Unsupported sentence type: {type(sentence)}")

    def _convert_gga(self, sentence: GGASentence, time: datetime) -> TelemetryPoint:
        """Convert GGA sentence to position telemetry point."""
        return TelemetryPoint(
            time=time,
            data_type=DataType.POSITION,
            latitude=sentence.latitude,
            longitude=sentence.longitude,
            value=sentence.altitude,  # altitude in meters
            source=self.source,
            sentence_type="GGA",
            raw_sentence=sentence.raw,
            quality=sentence.fix_quality.value,
            satellites=sentence.satellites,
            hdop=sentence.hdop,
        )

    def _convert_rmc(self, sentence: RMCSentence, time: datetime) -> list[TelemetryPoint]:
        """Convert RMC sentence to position, speed, and course telemetry points."""
        points = []

        # Position point
        if sentence.latitude is not None and sentence.longitude is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.POSITION,
                    latitude=sentence.latitude,
                    longitude=sentence.longitude,
                    source=self.source,
                    sentence_type="RMC",
                    raw_sentence=sentence.raw,
                    quality=1 if sentence.status == "A" else 0,
                )
            )

        # Speed point
        if sentence.speed_knots is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.SPEED,
                    value=sentence.speed_knots,
                    source=self.source,
                    sentence_type="RMC",
                    raw_sentence=sentence.raw,
                    metadata={"units": "knots"},
                )
            )

        # Course point
        if sentence.course is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.COURSE,
                    value=sentence.course,
                    source=self.source,
                    sentence_type="RMC",
                    raw_sentence=sentence.raw,
                    metadata={"units": "degrees_true"},
                )
            )

        return points

    def _convert_vtg(self, sentence: VTGSentence, time: datetime) -> list[TelemetryPoint]:
        """Convert VTG sentence to speed and course telemetry points."""
        points = []

        # Speed point (prefer knots)
        speed = sentence.speed_knots or (sentence.speed_kmh * 0.539957 if sentence.speed_kmh else None)
        if speed is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.SPEED,
                    value=speed,
                    source=self.source,
                    sentence_type="VTG",
                    raw_sentence=sentence.raw,
                    metadata={"units": "knots"},
                )
            )

        # Course point (true)
        if sentence.course_true is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.COURSE,
                    value=sentence.course_true,
                    source=self.source,
                    sentence_type="VTG",
                    raw_sentence=sentence.raw,
                    metadata={"units": "degrees_true"},
                )
            )

        return points

    def _convert_hdt(self, sentence: HDTSentence, time: datetime) -> TelemetryPoint:
        """Convert HDT sentence to heading telemetry point."""
        return TelemetryPoint(
            time=time,
            data_type=DataType.HEADING,
            value=sentence.heading,
            source=self.source,
            sentence_type="HDT",
            raw_sentence=sentence.raw,
            metadata={"units": "degrees_true"},
        )

    def _convert_hdg(self, sentence: HDGSentence, time: datetime) -> TelemetryPoint:
        """Convert HDG sentence to heading telemetry point."""
        metadata = {"units": "degrees_magnetic"}
        if sentence.deviation is not None:
            metadata["deviation"] = str(sentence.deviation)
        if sentence.variation is not None:
            metadata["variation"] = str(sentence.variation)

        return TelemetryPoint(
            time=time,
            data_type=DataType.HEADING,
            value=sentence.heading,
            source=self.source,
            sentence_type="HDG",
            raw_sentence=sentence.raw,
            metadata=metadata,
        )

    def _convert_dbt(self, sentence: DBTSentence, time: datetime) -> TelemetryPoint:
        """Convert DBT sentence to depth telemetry point."""
        # Prefer meters
        depth = sentence.depth_meters or (
            sentence.depth_feet * 0.3048 if sentence.depth_feet else None
        )

        return TelemetryPoint(
            time=time,
            data_type=DataType.DEPTH,
            value=depth,
            source=self.source,
            sentence_type="DBT",
            raw_sentence=sentence.raw,
            metadata={"units": "meters"},
        )

    def _convert_dpt(self, sentence: DPTSentence, time: datetime) -> TelemetryPoint:
        """Convert DPT sentence to depth telemetry point."""
        metadata = {"units": "meters"}
        if sentence.offset is not None:
            metadata["offset"] = str(sentence.offset)
        if sentence.max_range is not None:
            metadata["max_range"] = str(sentence.max_range)

        return TelemetryPoint(
            time=time,
            data_type=DataType.DEPTH,
            value=sentence.depth,
            source=self.source,
            sentence_type="DPT",
            raw_sentence=sentence.raw,
            metadata=metadata,
        )

    def _convert_mwv(self, sentence: MWVSentence, time: datetime) -> list[TelemetryPoint]:
        """Convert MWV sentence to wind speed and direction telemetry points."""
        points = []

        # Wind speed
        if sentence.wind_speed is not None and sentence.status == "A":
            # Convert to m/s if needed
            speed = sentence.wind_speed
            if sentence.speed_units == "K":  # km/h
                speed = speed / 3.6
            elif sentence.speed_units == "N":  # knots
                speed = speed * 0.514444

            metadata = {
                "units": "m/s",
                "reference": "relative" if sentence.reference == "R" else "true",
            }

            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.WIND_SPEED,
                    value=speed,
                    source=self.source,
                    sentence_type="MWV",
                    raw_sentence=sentence.raw,
                    metadata=metadata,
                )
            )

        # Wind direction
        if sentence.wind_angle is not None and sentence.status == "A":
            metadata = {
                "units": "degrees",
                "reference": "relative" if sentence.reference == "R" else "true",
            }

            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.WIND_DIRECTION,
                    value=sentence.wind_angle,
                    source=self.source,
                    sentence_type="MWV",
                    raw_sentence=sentence.raw,
                    metadata=metadata,
                )
            )

        return points

    def _convert_mtw(self, sentence: MTWSentence, time: datetime) -> TelemetryPoint:
        """Convert MTW sentence to water temperature telemetry point."""
        return TelemetryPoint(
            time=time,
            data_type=DataType.WATER_TEMPERATURE,
            value=sentence.temperature,
            source=self.source,
            sentence_type="MTW",
            raw_sentence=sentence.raw,
            metadata={"units": "celsius"},
        )

    def _convert_ais_position(
        self, message: AISPositionReport, time: datetime
    ) -> list[TelemetryPoint]:
        """Convert AIS position report to telemetry points."""
        points = []

        # Position point
        if message.latitude is not None and message.longitude is not None:
            metadata = {
                "mmsi": str(message.mmsi),
                "navigation_status": message.navigation_status.name,
                "position_accuracy": "high" if message.position_accuracy else "low",
            }

            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.AIS,
                    latitude=message.latitude,
                    longitude=message.longitude,
                    source="ais",
                    sentence_type="AIS_POSITION",
                    raw_sentence=message.raw,
                    metadata=metadata,
                )
            )

        # Speed point
        if message.speed_over_ground is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.SPEED,
                    value=message.speed_over_ground,
                    source="ais",
                    sentence_type="AIS_POSITION",
                    raw_sentence=message.raw,
                    metadata={"units": "knots", "mmsi": str(message.mmsi)},
                )
            )

        # Course point
        if message.course_over_ground is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.COURSE,
                    value=message.course_over_ground,
                    source="ais",
                    sentence_type="AIS_POSITION",
                    raw_sentence=message.raw,
                    metadata={"units": "degrees_true", "mmsi": str(message.mmsi)},
                )
            )

        # Heading point
        if message.true_heading is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.HEADING,
                    value=float(message.true_heading),
                    source="ais",
                    sentence_type="AIS_POSITION",
                    raw_sentence=message.raw,
                    metadata={"units": "degrees_true", "mmsi": str(message.mmsi)},
                )
            )

        # Rate of turn point
        if message.rate_of_turn is not None:
            points.append(
                TelemetryPoint(
                    time=time,
                    data_type=DataType.RATE_OF_TURN,
                    value=message.rate_of_turn,
                    source="ais",
                    sentence_type="AIS_POSITION",
                    raw_sentence=message.raw,
                    metadata={"units": "degrees_per_minute", "mmsi": str(message.mmsi)},
                )
            )

        return points

    def _convert_ais_static(self, message: AISStaticData, time: datetime) -> TelemetryPoint:
        """Convert AIS static data to custom telemetry point."""
        value_json = {
            "mmsi": message.mmsi,
            "vessel_name": message.vessel_name,
            "call_sign": message.call_sign,
            "ship_type": message.ship_type,
            "dimensions": {
                "bow": message.dimension_to_bow,
                "stern": message.dimension_to_stern,
                "port": message.dimension_to_port,
                "starboard": message.dimension_to_starboard,
            },
        }

        if message.imo_number:
            value_json["imo_number"] = message.imo_number
        if message.destination:
            value_json["destination"] = message.destination
        if message.draught:
            value_json["draught"] = message.draught
        if message.eta_month and message.eta_day:
            value_json["eta"] = {
                "month": message.eta_month,
                "day": message.eta_day,
                "hour": message.eta_hour,
                "minute": message.eta_minute,
            }

        return TelemetryPoint(
            time=time,
            data_type=DataType.AIS,
            source="ais",
            sentence_type="AIS_STATIC",
            raw_sentence=message.raw,
            value_json=value_json,
            metadata={"mmsi": str(message.mmsi)},
        )
