from __future__ import annotations

from pymulator.core.can import CANFrame
from pymulator.enet_service_broker.server import ENETServiceBrokerService, IsoTpBridge


def test_isotp_bridge_reassembles_local_frames_into_broker_pdu() -> None:
    broker_packets: list[tuple[int, bool, bytes]] = []
    can_writes: list[tuple[int, bool, bytes]] = []

    bridge = IsoTpBridge(
        send_broker_packet=lambda can_id, is_extended, payload: broker_packets.append((can_id, is_extended, payload)),
        write_can_frame=lambda can_id, is_extended, payload: can_writes.append((can_id, is_extended, payload)),
    )

    bridge.handle_local_frame(
        CANFrame(arbitration_id=0x7E0, data=[0x10, 0x09, 1, 2, 3, 4, 5, 6], dlc=8)
    )
    bridge.handle_local_frame(
        CANFrame(arbitration_id=0x7E0, data=[0x21, 7, 8, 9, 0, 0, 0, 0], dlc=8)
    )

    assert can_writes == [(0x7E8, False, bytes([0x30, 0x00, 0x00]))]
    assert broker_packets == [(0x7E0, False, bytes([1, 2, 3, 4, 5, 6, 7, 8, 9]))]


def test_isotp_bridge_segments_remote_pdu_after_flow_control() -> None:
    broker_packets: list[tuple[int, bool, bytes]] = []
    can_writes: list[tuple[int, bool, bytes]] = []

    bridge = IsoTpBridge(
        send_broker_packet=lambda can_id, is_extended, payload: broker_packets.append((can_id, is_extended, payload)),
        write_can_frame=lambda can_id, is_extended, payload: can_writes.append((can_id, is_extended, payload)),
    )

    payload = bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    bridge.enqueue_remote_pdu(can_id=0x7E0, is_extended=False, payload=payload)

    # First frame is sent immediately; remaining frames wait for FC.
    assert can_writes == [(0x7E0, False, bytes([0x10, 0x0A, 1, 2, 3, 4, 5, 6]))]

    # Clear-to-send from expected responder ID (0x7E8 for 0x7E0).
    bridge.handle_local_frame(
        CANFrame(arbitration_id=0x7E8, data=[0x30, 0x00, 0x00, 0, 0, 0, 0, 0], dlc=8)
    )

    assert can_writes == [
        (0x7E0, False, bytes([0x10, 0x0A, 1, 2, 3, 4, 5, 6])),
        (0x7E0, False, bytes([0x21, 7, 8, 9, 10])),
    ]
    assert broker_packets == []


class _CaptureBus:
    participants = {}
    last_activity = 0.0

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

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

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

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

    def recent_frames(self) -> list[str]:
        return []


def test_enet_service_injects_isotp_frames_as_dlc8_with_padding() -> None:
    bus = _CaptureBus()
    service = ENETServiceBrokerService(
        bus,
        vin="WBADE6324VBW12345",
        broker_url="https://esb.invalid.local",
        mode="isotp",
    )

    service._inject_isotp_can_frame(0x7E0, False, bytes([0x02, 0x3E, 0x00]))

    assert len(bus.writes) == 1
    frame = bus.writes[0]
    assert frame.arbitration_id == 0x7E0
    assert frame.dlc == 8
    assert bytes(frame.data[:8]) == bytes([0x02, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
