from __future__ import annotations

import pytest

from pymulator.core.can import CANFrame
from pymulator.core.external_can import ExternalCANBridge


class _DummyBus:
    def __init__(self) -> None:
        self.writes: list[CANFrame] = []

    def attach(self, _participant) -> None:
        return

    def detach(self, _participant) -> None:
        return

    def write(self, frame: CANFrame, originator) -> None:
        del originator
        self.writes.append(frame)


class _DummyCanInterface:
    def __init__(self, interface: str) -> None:
        self.interface = interface
        self.writes: list[CANFrame] = []

    def open(self, _bitrate: int, **_kwargs) -> None:
        return

    def close(self) -> None:
        return

    def read(self, timeout_ms: int) -> CANFrame:
        del timeout_ms
        raise AssertionError("read should not be called in this unit test")

    def write(self, frame: CANFrame) -> None:
        self.writes.append(frame)


def test_external_can_bridge_marks_recent_tx_as_self_echo(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr("pymulator.core.external_can.SocketCANInterface", _DummyCanInterface)
    bus = _DummyBus()
    bridge = ExternalCANBridge(bus, interface="gs_usb:0", bitrate=500000, log=False)
    frame = CANFrame(arbitration_id=0x7E0, data=[0x3E, 0x00], dlc=2)

    bridge._handle_bus_frame(frame)
    now = 1000.0
    assert bridge._is_recent_self_echo(frame, now=now) is True
    # Entry is consumed on first match.
    assert bridge._is_recent_self_echo(frame, now=now) is False


def test_external_can_bridge_does_not_drop_unrelated_frames(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr("pymulator.core.external_can.SocketCANInterface", _DummyCanInterface)
    bus = _DummyBus()
    bridge = ExternalCANBridge(bus, interface="gs_usb:0", bitrate=500000, log=False)
    tx_frame = CANFrame(arbitration_id=0x7E0, data=[0x22, 0xF1, 0x90], dlc=3)
    rx_frame = CANFrame(arbitration_id=0x7E8, data=[0x62, 0xF1, 0x90], dlc=3)

    bridge._handle_bus_frame(tx_frame)
    assert bridge._is_recent_self_echo(rx_frame, now=2000.0) is False
