from __future__ import annotations

from unittest.mock import MagicMock

import pytest
from tests.conftest import make_record

from mgf.tele.core import (
    DecodedAvlRecord,
    ReceivedPacket,
    SessionHandle,
    TelematicsProvider,
    TrackerCommand,
)
from mgf.tele.core.mock import MockExhaustedError, MockTelematicsProvider

_EXPECTED_ABSTRACTS = frozenset({"open_session", "receive_packet", "send_command", "close_session"})


def test_abstract_method_set_is_pinned() -> None:
    """A vendor implementation must satisfy exactly this set; drift here
    forces a deliberate cross-implementation review."""
    assert TelematicsProvider.__abstractmethods__ == _EXPECTED_ABSTRACTS


def test_mock_satisfies_the_abc() -> None:
    # Instantiation would raise TypeError if any abstract method were unfilled.
    MockTelematicsProvider(batches=[])


async def test_open_session_returns_handle_with_imei() -> None:
    provider = MockTelematicsProvider(batches=[], imei="356938035643809")
    handle = await provider.open_session(MagicMock(), MagicMock())
    assert isinstance(handle, SessionHandle)
    assert handle.imei == "356938035643809"


async def test_receive_packet_pops_scripted_batches_in_order() -> None:
    b1 = [make_record(speed_kmh=10.0)]
    b2 = [make_record(speed_kmh=20.0), make_record(speed_kmh=30.0)]
    provider = MockTelematicsProvider(batches=[b1, b2])
    handle = await provider.open_session(MagicMock(), MagicMock())

    pkt1 = await provider.receive_packet(handle)
    assert isinstance(pkt1, ReceivedPacket)
    assert pkt1.parse_status == "ok"
    assert [r.speed_kmh for r in pkt1.records] == [10.0]

    pkt2 = await provider.receive_packet(handle)
    assert [r.speed_kmh for r in pkt2.records] == [20.0, 30.0]


async def test_receive_packet_raises_when_exhausted() -> None:
    provider = MockTelematicsProvider(batches=[[make_record()]])
    handle = await provider.open_session(MagicMock(), MagicMock())
    await provider.receive_packet(handle)
    with pytest.raises(MockExhaustedError):
        await provider.receive_packet(handle)


async def test_send_command_is_not_implemented_read_only() -> None:
    provider = MockTelematicsProvider(batches=[])
    handle = await provider.open_session(MagicMock(), MagicMock())
    with pytest.raises(NotImplementedError):
        await provider.send_command(handle, TrackerCommand(payload=b"\x01"))


async def test_close_session_is_idempotent() -> None:
    provider = MockTelematicsProvider(batches=[])
    handle = await provider.open_session(MagicMock(), MagicMock())
    await provider.close_session(handle)
    await provider.close_session(handle)  # must not raise


def test_decoded_record_carries_codec_and_inputs() -> None:
    rec: DecodedAvlRecord = make_record()
    assert rec.codec == "C8"
    assert rec.inputs == {239: 1}
