"""Shared test fixtures for lc29h tests."""

import asyncio

import pytest

from lc29h.mcp_server import GNSSState
from lc29h.protocol import compute_checksum

# Sample NMEA sentences for testing (used by test_parser.py, test_protocol.py etc.)
SAMPLE_GGA = "$GPGGA,123519.00,4807.0381200,N,01131.0004100,E,1,08,0.9,545.4,M,47.0,M,,*47"
SAMPLE_RMC = "$GPRMC,123519.00,A,4807.0381200,N,01131.0004100,E,022.4,084.4,230326,003.1,W,A*3A"
SAMPLE_GSV = "$GPGSV,3,1,12,01,40,083,46,02,17,308,41,03,25,286,32,04,05,219,*75"
SAMPLE_PAIR001_OK = "$PAIR001,066,0*09"
SAMPLE_PAIR001_FAIL = "$PAIR001,066,1*08"
SAMPLE_PAIR021 = "$PAIR021,LC29HDANR01A01S_RSA*13"
SAMPLE_PQTMVERNO = "$PQTMVERNO,LC29HAANR11A01S,2021/09/28,7816279,LC29HAA,1.0.0*17"


# ---------------------------------------------------------------------------
# Mock serial transport infrastructure for MCP integration tests
# ---------------------------------------------------------------------------


def _fix_checksum(sentence: str) -> str:
    """Recompute checksum for a test NMEA sentence.

    Accepts any sentence with a $ prefix and *XX suffix, recalculates
    the XOR checksum, and returns the corrected sentence.
    """
    if "*" in sentence and sentence.startswith("$"):
        body = sentence[1:].split("*")[0]
        cs = compute_checksum(body)
        return f"${body}*{cs}"
    return sentence


# Real hardware NMEA data for MCP integration tests.
# This represents a single epoch: GSV (satellites), then GSA (fix details),
# then GGA (position fix), then RMC (recommended minimum).
# Checksums are recomputed to ensure validity.
HARDWARE_NMEA_STREAM = [
    _fix_checksum(s)
    for s in [
        # GPS satellites in view (2 messages, 8 total)
        "$GPGSV,2,1,08,06,53,055,22,11,69,170,34,19,15,297,18,25,28,307,26*00",
        "$GPGSV,2,2,08,31,13,040,,32,34,133,,36,25,196,,49,37,167,*00",
        # GLONASS satellites in view (2 messages, 6 total -- 4 in msg1 + 2 in msg2)
        "$GLGSV,2,1,06,75,54,155,27,76,14,024,,77,03,341,,78,11,072,*00",
        "$GLGSV,2,2,06,85,49,032,27,86,46,298,*00",
        # BeiDou satellites in view (1 message, 2 total)
        "$GBGSV,1,1,02,27,50,053,33,30,70,256,33*00",
        # GSA sentences (fix details) -- parser ignores these but they're realistic
        "$GNGSA,A,3,06,11,12,19,21,25,,,,,,,3.56,1.49,3.24,1*00",
        "$GNGSA,A,3,75,76,85,86,,,,,,,,,3.56,1.49,3.24,2*00",
        "$GNGSA,A,3,,,,,,,,,,,,,3.56,1.49,3.24,3*00",
        "$GNGSA,A,3,27,,,,,,,,,,,,3.56,1.49,3.24,4*00",
        "$GNGSA,A,3,,,,,,,,,,,,,3.56,1.49,3.24,5*00",
        # GGA: epoch boundary -- triggers GSV swap to live satellite list
        "$GNGGA,183520.00,4334.1234567,N,11629.1234567,W,1,10,1.49,830.0,M,-18.0,M,,*00",
        # RMC: speed and course
        "$GNRMC,183520.00,A,4334.1234567,N,11629.1234567,W,0.05,220.0,260226,,,A*00",
    ]
]


class MockSerialTransport:
    """Mock serial transport that replays NMEA lines for testing.

    Simulates the serial port by yielding pre-recorded lines and
    handling command/query patterns with canned responses. This lets
    us test MCP tool functions without real hardware.
    """

    def __init__(self, lines: list[str] | None = None):
        self._lines = list(lines or HARDWARE_NMEA_STREAM)
        self._index = 0
        self._monitor_active = False
        self._written: list[bytes] = []
        self._responses: dict[str, str] = {
            "$PAIR021": _fix_checksum("$PAIR021,AG3335A_V2.6.0.AG3335_20240319,D,N,7b9bf67*00"),
            "$PQTMVERNO": _fix_checksum("$PQTMVERNO,LC29HDANR11A03S_RSA,2024/03/19,13:55:31*00"),
            "$PQTMCFGDRMODE": _fix_checksum("$PQTMCFGDRMODE,OK,2*00"),
            "$PAIR491": _fix_checksum("$PAIR491,1,3*00"),
            "$PQTMCFGEAMASK": _fix_checksum("$PQTMCFGEAMASK,OK,5*00"),
            "$PAIR691": _fix_checksum("$PAIR691,0,1000,1000*00"),
            "$PQTMCFGSVIN": _fix_checksum(
                "$PQTMCFGSVIN,OK,1,1800,0.5,-2404572.0411,5381092.5507,2429899.7105*00"
            ),
        }

    async def connect(self):
        pass

    async def disconnect(self):
        pass

    async def write(self, data: bytes):
        """Record written data for assertion."""
        self._written.append(data)

    async def read_line(self, timeout: float = 2.0) -> str:
        """Return next pre-recorded NMEA line, cycling on exhaustion."""
        if self._index < len(self._lines):
            line = self._lines[self._index]
            self._index += 1
            return line
        self._index = 0
        await asyncio.sleep(0.01)
        raise TimeoutError("No more data")

    async def send_command(self, cmd: bytes, timeout: float = 2.0) -> str | None:
        """Return a successful ACK for any command."""
        self._written.append(cmd)
        return _fix_checksum("$PAIR001,066,0*00")

    async def query(self, cmd: bytes, response_prefix: str, timeout: float = 2.0) -> str | None:
        """Return canned response matching the expected prefix."""
        self._written.append(cmd)
        return self._responses.get(response_prefix)

    def dispatch_line(self, line: str) -> bool:
        return False


@pytest.fixture
def populated_gnss_state():
    """Create a GNSSState with mock transport and pre-parsed NMEA data.

    Feeds HARDWARE_NMEA_STREAM through the parser so that position,
    satellites, and other state are populated as they would be from
    a real hardware connection.
    """
    state = GNSSState()
    state.transport = MockSerialTransport()
    for line in HARDWARE_NMEA_STREAM:
        state.stream.parse_line(line)
    return state
