"""NMEA 0183 sentence parser."""

from fishinfo.exceptions import ValidationError

from .checksum import validate_checksum
from .sentences import (
    DBTSentence,
    DPTSentence,
    FixQuality,
    GGASentence,
    HDGSentence,
    HDTSentence,
    MTWSentence,
    MWVSentence,
    NMEASentence,
    RMCSentence,
    VTGSentence,
    parse_float,
    parse_int,
    parse_latitude,
    parse_longitude,
    parse_time,
)


class NMEAParser:
    """
    NMEA 0183 sentence parser.

    Supports common sentence types:
    - GGA: GPS Fix Data
    - RMC: Recommended Minimum
    - VTG: Course Over Ground
    - HDT/HDG: Heading
    - DBT/DPT: Depth
    - MWV: Wind Speed and Angle
    - MTW: Water Temperature
    """

    def __init__(self, validate_checksums: bool = True) -> None:
        """
        Initialize NMEA parser.

        Args:
            validate_checksums: Whether to validate sentence checksums
        """
        self.validate_checksums = validate_checksums

    def parse(self, sentence: str) -> NMEASentence:
        """
        Parse an NMEA 0183 sentence.

        Args:
            sentence: Complete NMEA sentence (e.g., "$GPGGA,...*47")

        Returns:
            Parsed sentence object

        Raises:
            ValidationError: If sentence is invalid or checksum fails

        Examples:
            >>> parser = NMEAParser()
            >>> sentence = parser.parse("$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,47.0,M,,*47")
            >>> sentence.latitude
            48.1173
        """
        # Strip whitespace
        sentence = sentence.strip()

        # Validate prefix
        if not sentence.startswith("$") and not sentence.startswith("!"):
            raise ValidationError(f"Invalid NMEA sentence: must start with $ or !: {sentence}")

        # Validate checksum if enabled
        if self.validate_checksums and "*" in sentence:
            if not validate_checksum(sentence):
                raise ValidationError(f"Invalid checksum: {sentence}")

        # Remove checksum for parsing
        if "*" in sentence:
            sentence = sentence.split("*")[0]

        # Remove prefix and split fields
        content = sentence[1:]
        fields = content.split(",")

        if not fields:
            raise ValidationError(f"Empty NMEA sentence: {sentence}")

        # Extract sentence type (last 3 characters of talker ID + type)
        sentence_id = fields[0]
        if len(sentence_id) < 3:
            raise ValidationError(f"Invalid sentence ID: {sentence_id}")

        sentence_type = sentence_id[-3:]

        # Parse based on sentence type
        if sentence_type == "GGA":
            return self._parse_gga(fields, sentence)
        elif sentence_type == "RMC":
            return self._parse_rmc(fields, sentence)
        elif sentence_type == "VTG":
            return self._parse_vtg(fields, sentence)
        elif sentence_type == "HDT":
            return self._parse_hdt(fields, sentence)
        elif sentence_type == "HDG":
            return self._parse_hdg(fields, sentence)
        elif sentence_type == "DBT":
            return self._parse_dbt(fields, sentence)
        elif sentence_type == "DPT":
            return self._parse_dpt(fields, sentence)
        elif sentence_type == "MWV":
            return self._parse_mwv(fields, sentence)
        elif sentence_type == "MTW":
            return self._parse_mtw(fields, sentence)
        else:
            raise ValidationError(f"Unsupported sentence type: {sentence_type}")

    def _parse_gga(self, fields: list[str], raw: str) -> GGASentence:
        """Parse GGA sentence."""
        if len(fields) < 15:
            raise ValidationError(f"Invalid GGA sentence: insufficient fields: {raw}")

        time = parse_time(fields[1])
        latitude = parse_latitude(fields[2], fields[3])
        longitude = parse_longitude(fields[4], fields[5])

        fix_quality_value = parse_int(fields[6])
        fix_quality = FixQuality(fix_quality_value) if fix_quality_value is not None else FixQuality.INVALID

        satellites = parse_int(fields[7])
        hdop = parse_float(fields[8])
        altitude = parse_float(fields[9])
        geoid_separation = parse_float(fields[11])

        return GGASentence(
            sentence_type="GGA",
            time=time,
            latitude=latitude,
            longitude=longitude,
            fix_quality=fix_quality,
            satellites=satellites,
            hdop=hdop,
            altitude=altitude,
            geoid_separation=geoid_separation,
            raw=raw,
        )

    def _parse_rmc(self, fields: list[str], raw: str) -> RMCSentence:
        """Parse RMC sentence."""
        if len(fields) < 12:
            raise ValidationError(f"Invalid RMC sentence: insufficient fields: {raw}")

        time = parse_time(fields[1], fields[9] if len(fields) > 9 else None)
        status = fields[2] if len(fields) > 2 else "V"
        latitude = parse_latitude(fields[3], fields[4]) if len(fields) > 4 else None
        longitude = parse_longitude(fields[5], fields[6]) if len(fields) > 6 else None
        speed_knots = parse_float(fields[7]) if len(fields) > 7 else None
        course = parse_float(fields[8]) if len(fields) > 8 else None

        # Magnetic variation (optional)
        magnetic_variation = None
        if len(fields) > 10 and fields[10]:
            var = parse_float(fields[10])
            if var is not None and len(fields) > 11 and fields[11]:
                # Apply direction (E = positive, W = negative)
                if fields[11] == "W":
                    var = -var
                magnetic_variation = var

        return RMCSentence(
            sentence_type="RMC",
            time=time,
            status=status,
            latitude=latitude,
            longitude=longitude,
            speed_knots=speed_knots,
            course=course,
            date=time,  # time includes date if provided
            magnetic_variation=magnetic_variation,
            raw=raw,
        )

    def _parse_vtg(self, fields: list[str], raw: str) -> VTGSentence:
        """Parse VTG sentence."""
        if len(fields) < 9:
            raise ValidationError(f"Invalid VTG sentence: insufficient fields: {raw}")

        course_true = parse_float(fields[1])
        course_magnetic = parse_float(fields[3])
        speed_knots = parse_float(fields[5])
        speed_kmh = parse_float(fields[7])

        return VTGSentence(
            sentence_type="VTG",
            course_true=course_true,
            course_magnetic=course_magnetic,
            speed_knots=speed_knots,
            speed_kmh=speed_kmh,
            raw=raw,
        )

    def _parse_hdt(self, fields: list[str], raw: str) -> HDTSentence:
        """Parse HDT sentence."""
        if len(fields) < 2:
            raise ValidationError(f"Invalid HDT sentence: insufficient fields: {raw}")

        heading = parse_float(fields[1])

        return HDTSentence(
            sentence_type="HDT",
            heading=heading,
            raw=raw,
        )

    def _parse_hdg(self, fields: list[str], raw: str) -> HDGSentence:
        """Parse HDG sentence."""
        if len(fields) < 5:
            raise ValidationError(f"Invalid HDG sentence: insufficient fields: {raw}")

        heading = parse_float(fields[1])

        # Deviation (optional)
        deviation = None
        if len(fields) > 2 and fields[2]:
            dev = parse_float(fields[2])
            if dev is not None and len(fields) > 3 and fields[3]:
                # Apply direction (E = positive, W = negative)
                if fields[3] == "W":
                    dev = -dev
                deviation = dev

        # Variation (optional)
        variation = None
        if len(fields) > 4 and fields[4]:
            var = parse_float(fields[4])
            if var is not None and len(fields) > 5 and fields[5]:
                # Apply direction (E = positive, W = negative)
                if fields[5] == "W":
                    var = -var
                variation = var

        return HDGSentence(
            sentence_type="HDG",
            heading=heading,
            deviation=deviation,
            variation=variation,
            raw=raw,
        )

    def _parse_dbt(self, fields: list[str], raw: str) -> DBTSentence:
        """Parse DBT sentence."""
        if len(fields) < 7:
            raise ValidationError(f"Invalid DBT sentence: insufficient fields: {raw}")

        depth_feet = parse_float(fields[1])
        depth_meters = parse_float(fields[3])
        depth_fathoms = parse_float(fields[5])

        return DBTSentence(
            sentence_type="DBT",
            depth_feet=depth_feet,
            depth_meters=depth_meters,
            depth_fathoms=depth_fathoms,
            raw=raw,
        )

    def _parse_dpt(self, fields: list[str], raw: str) -> DPTSentence:
        """Parse DPT sentence."""
        if len(fields) < 2:
            raise ValidationError(f"Invalid DPT sentence: insufficient fields: {raw}")

        depth = parse_float(fields[1])
        offset = parse_float(fields[2]) if len(fields) > 2 else None
        max_range = parse_float(fields[3]) if len(fields) > 3 else None

        return DPTSentence(
            sentence_type="DPT",
            depth=depth,
            offset=offset,
            max_range=max_range,
            raw=raw,
        )

    def _parse_mwv(self, fields: list[str], raw: str) -> MWVSentence:
        """Parse MWV sentence."""
        if len(fields) < 6:
            raise ValidationError(f"Invalid MWV sentence: insufficient fields: {raw}")

        wind_angle = parse_float(fields[1])
        reference = fields[2] if len(fields) > 2 else ""
        wind_speed = parse_float(fields[3])
        speed_units = fields[4] if len(fields) > 4 else ""
        status = fields[5] if len(fields) > 5 else "V"

        return MWVSentence(
            sentence_type="MWV",
            wind_angle=wind_angle,
            reference=reference,
            wind_speed=wind_speed,
            speed_units=speed_units,
            status=status,
            raw=raw,
        )

    def _parse_mtw(self, fields: list[str], raw: str) -> MTWSentence:
        """Parse MTW sentence."""
        if len(fields) < 2:
            raise ValidationError(f"Invalid MTW sentence: insufficient fields: {raw}")

        temperature = parse_float(fields[1])
        units = fields[2] if len(fields) > 2 else "C"

        return MTWSentence(
            sentence_type="MTW",
            temperature=temperature,
            units=units,
            raw=raw,
        )
