"""PAIR + PQTM command builders for Quectel LC29H modules.

Each function returns the framed command body (without $ prefix and *XX suffix).
Use protocol.frame_command() to produce bytes for serial transmission.

Command catalog derived from:
  - Quectel_LC29H_GNSS_Protocol_Specification_V1.1.pdf
  - Quectel_LC29H_BS_Protocol_Specification_V1.0.pdf
  - QGNSS.exe decompilation (Ghidra)
"""

from .protocol import frame_command

# ---------------------------------------------------------------------------
# System commands (PAIR003-PAIR021)
# ---------------------------------------------------------------------------


def pair_cold_start() -> bytes:
    """PAIR003: Cold start — clear all data and restart.

    Clears ephemeris, almanac, position, and time.
    """
    return frame_command("PAIR003")


def pair_hot_start() -> bytes:
    """PAIR004: Hot start — restart with all data intact."""
    return frame_command("PAIR004")


def pair_warm_start() -> bytes:
    """PAIR005: Warm start — clear ephemeris only."""
    return frame_command("PAIR005")


def pair_cold_start_no_data() -> bytes:
    """PAIR006: Cold start without clearing system/user config."""
    return frame_command("PAIR006")


def pair_factory_reset() -> bytes:
    """PAIR007: Full factory reset — clear everything."""
    return frame_command("PAIR007")


def pair_get_version() -> bytes:
    """PAIR021: Query firmware version.

    Response: $PAIR021,<FW_Version>*CS
    """
    return frame_command("PAIR021")


# ---------------------------------------------------------------------------
# Fix rate and serial config (PAIR050-PAIR051)
# ---------------------------------------------------------------------------


def pair_set_fix_rate(ms: int) -> bytes:
    """PAIR050: Set position fix interval.

    Args:
        ms: Fix interval in milliseconds (100-10000).
            100ms = 10Hz, 1000ms = 1Hz.
    """
    return frame_command(f"PAIR050,{ms}")


def pair_set_baud_rate(rate: int) -> bytes:
    """PAIR051: Set UART1 baud rate.

    Args:
        rate: Baud rate. Common values: 4800, 9600, 19200, 38400,
              57600, 115200, 230400, 460800, 921600.
    """
    return frame_command(f"PAIR051,{rate}")


# ---------------------------------------------------------------------------
# DGPS mode (PAIR062)
# ---------------------------------------------------------------------------


def pair_set_dgps_mode(mode: int) -> bytes:
    """PAIR062: Set DGPS correction mode.

    Args:
        mode: 0=off, 1=RTCM, 2=SBAS.
    """
    return frame_command(f"PAIR062,{mode}")


# ---------------------------------------------------------------------------
# GNSS constellation config (PAIR066)
# ---------------------------------------------------------------------------


def pair_set_constellation(
    gps: bool = True,
    glonass: bool = True,
    galileo: bool = True,
    beidou: bool = True,
    qzss: bool = True,
) -> bytes:
    """PAIR066: Configure which GNSS constellations to use.

    From QGNSS.exe decompilation — uses bitmask format:
    GPS=1, GLONASS=2, Galileo=4, BeiDou=8, QZSS=16.
    Each parameter is 0 (disabled) or 1 (enabled).
    """
    return frame_command(
        f"PAIR066,"
        f"{int(gps)},"
        f"{int(glonass)},"
        f"{int(galileo)},"
        f"{int(beidou)},"
        f"{int(qzss)}"
    )


# ---------------------------------------------------------------------------
# NMEA output control (PAIR070-PAIR080)
# ---------------------------------------------------------------------------


def pair_set_nmea_output(
    gll: int = 0,
    rmc: int = 1,
    vtg: int = 0,
    gga: int = 1,
    gsa: int = 1,
    gsv: int = 1,
    gns: int = 0,
) -> bytes:
    """PAIR070-PAIR080: Set NMEA sentence output rates.

    Args:
        Each parameter is the output rate (0=disabled, 1=every fix,
        2=every 2nd fix, etc.).
    """
    cmds = [
        frame_command(f"PAIR070,{gll}"),
        frame_command(f"PAIR071,{rmc}"),
        frame_command(f"PAIR072,{vtg}"),
        frame_command(f"PAIR073,{gga}"),
        frame_command(f"PAIR074,{gsa}"),
        frame_command(f"PAIR075,{gsv}"),
        frame_command(f"PAIR076,{gns}"),
    ]
    return b"".join(cmds)


def pair_set_gll_output(rate: int) -> bytes:
    """PAIR070: Set GLL output rate."""
    return frame_command(f"PAIR070,{rate}")


def pair_set_rmc_output(rate: int) -> bytes:
    """PAIR071: Set RMC output rate."""
    return frame_command(f"PAIR071,{rate}")


def pair_set_vtg_output(rate: int) -> bytes:
    """PAIR072: Set VTG output rate."""
    return frame_command(f"PAIR072,{rate}")


def pair_set_gga_output(rate: int) -> bytes:
    """PAIR073: Set GGA output rate."""
    return frame_command(f"PAIR073,{rate}")


def pair_set_gsa_output(rate: int) -> bytes:
    """PAIR074: Set GSA output rate."""
    return frame_command(f"PAIR074,{rate}")


def pair_set_gsv_output(rate: int) -> bytes:
    """PAIR075: Set GSV output rate."""
    return frame_command(f"PAIR075,{rate}")


def pair_set_gns_output(rate: int) -> bytes:
    """PAIR076: Set GNS output rate."""
    return frame_command(f"PAIR076,{rate}")


# ---------------------------------------------------------------------------
# SBAS config (PAIR382-PAIR410)
# ---------------------------------------------------------------------------


def pair_set_sbas(enabled: bool) -> bytes:
    """PAIR382: Enable/disable SBAS augmentation.

    SBAS supports WAAS, EGNOS, MSAS, GAGAN.
    AA variant only.
    """
    return frame_command(f"PAIR382,{int(enabled)}")


def pair_get_sbas() -> bytes:
    """PAIR383: Query SBAS status."""
    return frame_command("PAIR383")


# ---------------------------------------------------------------------------
# RTCM output config (PAIR432-PAIR437) — for DA/BS variants
# ---------------------------------------------------------------------------


def pair_set_rtcm_mode(mode: int) -> bytes:
    """PAIR432: Set RTCM output mode.

    Args:
        mode: -1=disabled, 0=MSM4, 1=MSM7.
    """
    return frame_command(f"PAIR432,{mode}")


def pair_get_rtcm_mode() -> bytes:
    """PAIR433: Query RTCM output mode."""
    return frame_command("PAIR433")


def pair_set_rtcm_antenna_ref(enabled: bool) -> bytes:
    """PAIR434: Enable/disable RTCM antenna reference point (msg 1005)."""
    return frame_command(f"PAIR434,{int(enabled)}")


def pair_get_rtcm_antenna_ref() -> bytes:
    """PAIR435: Query RTCM antenna reference point config."""
    return frame_command("PAIR435")


def pair_set_rtcm_ephemeris(enabled: bool) -> bytes:
    """PAIR436: Enable/disable RTCM satellite ephemeris output."""
    return frame_command(f"PAIR436,{int(enabled)}")


def pair_get_rtcm_ephemeris() -> bytes:
    """PAIR437: Query RTCM ephemeris output config."""
    return frame_command("PAIR437")


# ---------------------------------------------------------------------------
# EASY / AGNSS (PAIR470-PAIR491) — AA variant
# ---------------------------------------------------------------------------


def pair_set_easy(enabled: bool) -> bytes:
    """PAIR490: Enable/disable EASY (self-generated ephemeris prediction).

    EASY caches satellite data and predicts orbits for 3 days,
    allowing faster TTFF when signal is temporarily lost.
    AA variant only.
    """
    return frame_command(f"PAIR490,{int(enabled)}")


def pair_get_easy_status() -> bytes:
    """PAIR491: Query EASY status.

    Response: $PAIR491,<Enabled>,<ExtensionDays>*CS
    ExtensionDays: 0-3 (days of extended ephemeris available).
    """
    return frame_command("PAIR491")


def pair_get_epo_status() -> bytes:
    """PAIR470: Query EPO (Extended Prediction Orbit) status."""
    return frame_command("PAIR470")


def pair_epo_inject(data_fields: list[str]) -> bytes:
    """PAIR471: Inject EPO data segment.

    From QGNSS.exe decompilation: 20 hex-encoded fields per segment.
    The full EPO file is split into chunks and sent as sequential PAIR471s.

    Args:
        data_fields: List of hex-encoded data fields.
    """
    fields = ",".join(data_fields)
    return frame_command(f"PAIR471,{fields}")


def pair_epo_erase() -> bytes:
    """PAIR472: Erase EPO data from flash."""
    return frame_command("PAIR472")


# ---------------------------------------------------------------------------
# Time and position assist (PAIR590, PAIR600) — for AGNSS
# ---------------------------------------------------------------------------


def pair_time_assist(year: int, month: int, day: int, hour: int, minute: int, second: int) -> bytes:
    """PAIR590: Inject UTC time reference for AGNSS.

    Provides the module with approximate current time to speed up
    satellite acquisition.
    """
    return frame_command(f"PAIR590,{year},{month},{day},{hour},{minute},{second}")


def pair_position_assist(lat: float, lon: float, alt: float = 0.0) -> bytes:
    """PAIR600: Inject approximate position for AGNSS.

    From QGNSS.exe: uses hardcoded uncertainty values.

    Args:
        lat: Latitude in decimal degrees.
        lon: Longitude in decimal degrees.
        alt: Altitude in meters (default 0).
    """
    return frame_command(f"PAIR600,{lat:.6f},{lon:.6f},{alt:.1f}")


# ---------------------------------------------------------------------------
# NVRAM persistence (PAIR511, PAIR513)
# ---------------------------------------------------------------------------


def pair_save_nav_data() -> bytes:
    """PAIR511: Save current navigation data to flash."""
    return frame_command("PAIR511")


def pair_save_config() -> bytes:
    """PAIR513: Save current settings to flash (persist across reboot)."""
    return frame_command("PAIR513")


# ---------------------------------------------------------------------------
# Power management (PAIR650, PAIR690-PAIR691)
# ---------------------------------------------------------------------------


def pair_enter_backup_mode() -> bytes:
    """PAIR650: Enter RTC backup (ultra-low power) mode.

    Module stops GNSS and enters ~7uA sleep. Wake via pin or RTC alarm.
    """
    return frame_command("PAIR650")


def pair_set_periodic_mode(
    mode: int, run_ms: int = 1000, sleep_ms: int = 1000
) -> bytes:
    """PAIR690: Configure periodic power saving mode.

    Args:
        mode: 0=disabled, 1=smart (position-aware), 2=strict (fixed timing).
        run_ms: GNSS run duration in milliseconds.
        sleep_ms: Sleep duration in milliseconds.
    """
    return frame_command(f"PAIR690,{mode},{run_ms},{sleep_ms}")


def pair_get_periodic_mode() -> bytes:
    """PAIR691: Query periodic power saving mode."""
    return frame_command("PAIR691")


# ---------------------------------------------------------------------------
# PPS output (PAIR752)
# ---------------------------------------------------------------------------


def pair_set_pps(
    pps_type: int = 1, pulse_width_ms: int = 100
) -> bytes:
    """PAIR752: Configure PPS (Pulse Per Second) output.

    Args:
        pps_type: 0=disabled, 1=after first fix, 2=3D fix only,
                  3=2D/3D fix, 4=always on.
        pulse_width_ms: Pulse width 1-999 ms.
    """
    return frame_command(f"PAIR752,{pps_type},{pulse_width_ms}")


# ---------------------------------------------------------------------------
# Raw measurement output (PAIR830-PAIR831)
# ---------------------------------------------------------------------------


def pair_set_raw_measurement(mode: int) -> bytes:
    """PAIR830: Enable raw measurement output (binary RTCM-like).

    Args:
        mode: 0=disabled, 1=raw measurements, 2=raw+SV+PVT.
    """
    return frame_command(f"PAIR830,{mode}")


def pair_get_raw_measurement() -> bytes:
    """PAIR831: Query raw measurement output status."""
    return frame_command("PAIR831")


# ---------------------------------------------------------------------------
# UART port config (PAIR864-PAIR867)
# ---------------------------------------------------------------------------


def pair_set_port_baud(port_type: int, port_index: int, baud: int) -> bytes:
    """PAIR864: Set baud rate for a specific port.

    Args:
        port_type: 0=UART.
        port_index: Port number (0=UART1, 1=UART2).
        baud: 4800, 9600, 19200, 38400, 57600, 115200, 230400,
              460800, 921600, 1500000, 3000000.
    """
    return frame_command(f"PAIR864,{port_type},{port_index},{baud}")


def pair_get_port_baud(port_type: int, port_index: int) -> bytes:
    """PAIR865: Query port baud rate."""
    return frame_command(f"PAIR865,{port_type},{port_index}")


def pair_set_flow_control(port_type: int, port_index: int, mode: int) -> bytes:
    """PAIR866: Set flow control mode.

    Args:
        port_type: 0=UART.
        port_index: Port number.
        mode: 0=disabled, 1=software (XON/XOFF), 2=hardware (RTS/CTS).
    """
    return frame_command(f"PAIR866,{port_type},{port_index},{mode}")


def pair_get_flow_control(port_type: int, port_index: int) -> bytes:
    """PAIR867: Query flow control mode."""
    return frame_command(f"PAIR867,{port_type},{port_index}")


# ===========================================================================
# PQTM Commands — Quectel proprietary extensions
# ===========================================================================


def pqtm_version() -> bytes:
    """PQTMVERNO: Query firmware version.

    Response: $PQTMVERNO,<FW>,<HW>,<ID>,<SDK>,<Date>*CS
    """
    return frame_command("PQTMVERNO")


def pqtm_save_params() -> bytes:
    """PQTMSAVEPAR: Save all parameters to flash."""
    return frame_command("PQTMSAVEPAR")


def pqtm_restore_params() -> bytes:
    """PQTMRESTOREPAR: Restore factory default parameters."""
    return frame_command("PQTMRESTOREPAR")


def pqtm_restart(restart_type: int = 1) -> bytes:
    """PQTMGNSSRESTART: Restart GNSS engine.

    Args:
        restart_type: 1=hot, 2=warm, 3=cold, 4=full cold.
    """
    return frame_command(f"PQTMGNSSRESTART,{restart_type}")


def pqtm_suspend() -> bytes:
    """PQTMGNSSSUSPEND: Suspend GNSS engine (enter idle)."""
    return frame_command("PQTMGNSSSUSPEND")


def pqtm_resume() -> bytes:
    """PQTMGNSSRESUME: Resume GNSS engine from suspend."""
    return frame_command("PQTMGNSSRESUME")


# ---------------------------------------------------------------------------
# Port config (PQTMCFGPORT)
# ---------------------------------------------------------------------------


def pqtm_cfg_port(
    port: str = "UART1",
    baud: int = 115200,
    input_protos: str = "NMEA|RTCM3",
    output_protos: str = "NMEA",
) -> bytes:
    """PQTMCFGPORT: Configure port protocol and baud rate.

    Args:
        port: Port name (UART1, UART2, USB).
        baud: Baud rate.
        input_protos: Input protocols (e.g. "NMEA", "RTCM3", "NMEA|RTCM3").
        output_protos: Output protocols.
    """
    return frame_command(f"PQTMCFGPORT,W,{port},{baud},{input_protos},{output_protos}")


def pqtm_get_port(port: str = "UART1") -> bytes:
    """PQTMCFGPORT: Query port configuration."""
    return frame_command(f"PQTMCFGPORT,R,{port}")


# ---------------------------------------------------------------------------
# NMEA message config (PQTMCFGNMEAMSG, PQTMCFGMSGRATE)
# ---------------------------------------------------------------------------


def pqtm_cfg_nmea_msg(msg_type: str, uart1: int = 1, uart2: int = 0) -> bytes:
    """PQTMCFGNMEAMSG: Configure NMEA message output per port.

    Args:
        msg_type: Message type (e.g. "GGA", "RMC", "GSV").
        uart1: Output rate on UART1 (0=disabled).
        uart2: Output rate on UART2 (0=disabled).
    """
    return frame_command(f"PQTMCFGNMEAMSG,W,{msg_type},{uart1},{uart2}")


def pqtm_cfg_msg_rate(msg_type: str, rate: int, port: int = 1) -> bytes:
    """PQTMCFGMSGRATE: Set output rate for any message type.

    Args:
        msg_type: Full message name (e.g. "PQTMSVINSTATUS", "GGA").
        rate: Rate (0=disabled, 1=every epoch, etc.).
        port: Port index (1=UART1).
    """
    return frame_command(f"PQTMCFGMSGRATE,W,{msg_type},{rate},{port}")


# ---------------------------------------------------------------------------
# Elevation mask (PQTMCFGEAMASK)
# ---------------------------------------------------------------------------


def pqtm_cfg_elevation_mask(degrees: int = 5) -> bytes:
    """PQTMCFGEAMASK: Set satellite elevation mask angle.

    Args:
        degrees: Minimum elevation in degrees (0-90, default 5).
    """
    return frame_command(f"PQTMCFGEAMASK,W,{degrees}")


def pqtm_get_elevation_mask() -> bytes:
    """PQTMCFGEAMASK: Query elevation mask angle."""
    return frame_command("PQTMCFGEAMASK,R")


# ---------------------------------------------------------------------------
# Speed clamping (PQTMCFGCLAMPING)
# ---------------------------------------------------------------------------


def pqtm_cfg_clamping(enabled: bool) -> bytes:
    """PQTMCFGCLAMPING: Enable/disable static speed clamping.

    When enabled, reported speed is zeroed when stationary.
    """
    return frame_command(f"PQTMCFGCLAMPING,W,{int(enabled)}")


# ---------------------------------------------------------------------------
# Odometer (PQTMCFGODO, PQTMRESETODO)
# ---------------------------------------------------------------------------


def pqtm_cfg_odometer(enabled: bool) -> bytes:
    """PQTMCFGODO: Enable/disable odometer."""
    return frame_command(f"PQTMCFGODO,W,{int(enabled)}")


def pqtm_reset_odometer() -> bytes:
    """PQTMRESETODO: Reset odometer distance to zero."""
    return frame_command("PQTMRESETODO")


# ---------------------------------------------------------------------------
# Geofence (PQTMCFGGEOFENCE)
# ---------------------------------------------------------------------------


def pqtm_cfg_geofence(
    fence_id: int, lat: float, lon: float, radius: float, enabled: bool = True
) -> bytes:
    """PQTMCFGGEOFENCE: Configure a geofence circle.

    Args:
        fence_id: Geofence ID (0-3).
        lat: Center latitude in decimal degrees.
        lon: Center longitude in decimal degrees.
        radius: Radius in meters.
        enabled: Enable or disable this fence.
    """
    return frame_command(
        f"PQTMCFGGEOFENCE,W,{fence_id},{int(enabled)},{lat:.8f},{lon:.8f},{radius:.1f}"
    )


# ---------------------------------------------------------------------------
# Dead Reckoning (PQTMCFGDRMODE) — BA/CA variants
# ---------------------------------------------------------------------------


def pqtm_cfg_dr_mode(mode: int) -> bytes:
    """PQTMCFGDRMODE: Set dead reckoning mode.

    Args:
        mode: 0=GNSS only, 1=DR only, 2=GNSS+DR (auto).
    """
    return frame_command(f"PQTMCFGDRMODE,W,{mode}")


def pqtm_get_dr_mode() -> bytes:
    """PQTMCFGDRMODE: Query DR mode."""
    return frame_command("PQTMCFGDRMODE,R")


# ---------------------------------------------------------------------------
# Forward mode (PQTMCFGFWD)
# ---------------------------------------------------------------------------


def pqtm_cfg_forward(enabled: bool) -> bytes:
    """PQTMCFGFWD: Enable/disable UART forwarding mode."""
    return frame_command(f"PQTMCFGFWD,W,{int(enabled)}")


# ---------------------------------------------------------------------------
# Wheel tick (PQTMCFGWHEELTICK) — BA/CA variants
# ---------------------------------------------------------------------------


def pqtm_cfg_wheel_tick(
    enabled: bool, ticks_per_rev: int = 1, wheel_circumference: float = 1.0
) -> bytes:
    """PQTMCFGWHEELTICK: Configure wheel tick input for DR.

    Args:
        enabled: Enable wheel tick input.
        ticks_per_rev: Pulses per wheel revolution.
        wheel_circumference: Wheel circumference in meters.
    """
    return frame_command(
        f"PQTMCFGWHEELTICK,W,{int(enabled)},{ticks_per_rev},{wheel_circumference:.4f}"
    )


# ---------------------------------------------------------------------------
# CAN bus (PQTMCFGCAN, PQTMCFGCANFILTER, PQTMCFGVEHDBC) — BA variant
# ---------------------------------------------------------------------------


def pqtm_cfg_can(baud: int = 500000, enabled: bool = True) -> bytes:
    """PQTMCFGCAN: Configure CAN bus interface.

    Args:
        baud: CAN bus baud rate (typically 500000).
        enabled: Enable CAN bus interface.
    """
    return frame_command(f"PQTMCFGCAN,W,{int(enabled)},{baud}")


def pqtm_cfg_can_filter(filter_id: int, can_id: int, mask: int) -> bytes:
    """PQTMCFGCANFILTER: Configure CAN message filter.

    Args:
        filter_id: Filter index.
        can_id: CAN message ID to match.
        mask: Bit mask for ID matching.
    """
    return frame_command(f"PQTMCFGCANFILTER,W,{filter_id},{can_id},{mask}")


def pqtm_cfg_vehicle_dbc(dbc_config: str) -> bytes:
    """PQTMCFGVEHDBC: Configure vehicle DBC (CAN database) for DR.

    Args:
        dbc_config: DBC configuration string.
    """
    return frame_command(f"PQTMCFGVEHDBC,W,{dbc_config}")


# ---------------------------------------------------------------------------
# INS status output (PQTMCFGEINSMSG) — BA/CA variants
# ---------------------------------------------------------------------------


def pqtm_cfg_eins_msg(enabled: bool) -> bytes:
    """PQTMCFGEINSMSG: Enable/disable extended INS status messages.

    When enabled, outputs additional DR/INS calibration data.
    """
    return frame_command(f"PQTMCFGEINSMSG,W,{int(enabled)}")


# ---------------------------------------------------------------------------
# Survey-in (PQTMCFGSVIN) — BS variant only
# ---------------------------------------------------------------------------


def pqtm_cfg_survey_in(
    mode: int = 1,
    duration_s: int = 3600,
    accuracy_m: float = 1.0,
    ecef_x: float = 0.0,
    ecef_y: float = 0.0,
    ecef_z: float = 0.0,
) -> bytes:
    """PQTMCFGSVIN: Configure survey-in for base station.

    Args:
        mode: 0=disabled, 1=survey-in, 2=fixed position.
        duration_s: Minimum survey-in duration in seconds.
        accuracy_m: Required accuracy in meters.
        ecef_x/y/z: Fixed ECEF coordinates (mode=2 only).
    """
    return frame_command(
        f"PQTMCFGSVIN,W,{mode},{duration_s},{accuracy_m:.1f},{ecef_x:.4f},{ecef_y:.4f},{ecef_z:.4f}"
    )


def pqtm_get_survey_in() -> bytes:
    """PQTMCFGSVIN: Query survey-in configuration."""
    return frame_command("PQTMCFGSVIN,R")


def pqtm_get_survey_in_status() -> bytes:
    """PQTMSVINSTATUS: Query survey-in progress.

    Enable status output first: pqtm_cfg_msg_rate("PQTMSVINSTATUS", 1)
    """
    return frame_command("PQTMCFGMSGRATE,R,PQTMSVINSTATUS")


# ---------------------------------------------------------------------------
# Firmware upgrade (PQTMFWUPGRADE)
# ---------------------------------------------------------------------------


def pqtm_firmware_upgrade() -> bytes:
    """PQTMFWUPGRADE: Enter firmware upgrade mode.

    Module enters bootloader and waits for firmware via UART.
    """
    return frame_command("PQTMFWUPGRADE")
