import pytest

from ecu_framework.lin.base import LinFrame
from ecu_framework.lin.babylin import BabyLinInterface

# Inject the pure-Python mock wrapper to run SDK adapter tests without hardware
from vendor import mock_babylin_wrapper as mock_bl


class _MockBytesOnly:
    """Shim exposing BLC_sendRawMasterRequest(bytes) only, to test bytes signature.

    We wrap the existing mock but override BLC_sendRawMasterRequest to accept
    only the bytes payload form. The response still uses the deterministic pattern
    implied by the payload length (zeros are fine; we assert by length here).
    """

    @staticmethod
    def create_BabyLIN():
        base = mock_bl.create_BabyLIN()

        def bytes_only(channel, frame_id, payload):
            # Delegate to the base mock's bytes variant by ensuring we pass bytes
            if not isinstance(payload, (bytes, bytearray)):
                raise TypeError("expected bytes payload")
            return base.BLC_sendRawMasterRequest(channel, frame_id, bytes(payload))

        # Monkey-patch the method to raise TypeError when a length is provided
        def patched_raw_req(*args):
            # Expected signature: (channel, frame_id, payload_bytes)
            if len(args) != 3 or not isinstance(args[2], (bytes, bytearray)):
                raise TypeError("bytes signature only")
            return bytes_only(*args)

        base.BLC_sendRawMasterRequest = patched_raw_req
        return base


@pytest.mark.babylin
@pytest.mark.smoke
@pytest.mark.req_001
def test_babylin_sdk_adapter_with_mock_wrapper(rp):
    """
    Title: SDK Adapter - Send/Receive with Mock Wrapper

    Description:
        Validate that the BabyLIN SDK-based adapter can send and receive using
        a mocked wrapper exposing BLC_* APIs. The mock implements loopback by
        echoing transmitted frames into the receive queue.

    Requirements: REQ-001

    Test Steps:
        1. Construct BabyLinInterface with injected mock wrapper
        2. Connect (discovers port, opens, loads SDF, starts schedule)
        3. Send a frame via BLC_mon_set_xmit
        4. Receive the same frame via BLC_getNextFrameTimeout
        5. Disconnect

    Expected Result:
        - Received frame matches sent frame (ID and payload)
    """
    # Step 1-2: Create adapter with wrapper injection and connect
    lin = BabyLinInterface(sdf_path="./vendor/Example.sdf", schedule_nr=0, wrapper_module=mock_bl)
    rp("wrapper", "mock_bl")
    lin.connect()
    try:
        # Step 3: Transmit a known payload on a chosen ID
        tx = LinFrame(id=0x12, data=bytes([0xAA, 0x55, 0x01]))
        lin.send(tx)

        # Step 4: Receive from the mock's RX queue (loopback)
        rx = lin.receive(timeout=0.1)
        rp("tx_id", f"0x{tx.id:02X}")
        rp("tx_data", list(tx.data))
        rp("rx_present", rx is not None)

        # Step 5: Validate ID and payload integrity
        assert rx is not None, "Expected a frame from mock loopback"
        assert rx.id == tx.id
        assert rx.data == tx.data
    finally:
        # Always disconnect to leave the mock in a clean state
        lin.disconnect()


@pytest.mark.babylin
@pytest.mark.smoke
@pytest.mark.req_001
@pytest.mark.parametrize("wrapper,expect_pattern", [
    (mock_bl, True),          # length signature available: expect deterministic pattern
    (_MockBytesOnly, False),  # bytes-only signature: expect zeros of requested length
])
def test_babylin_master_request_with_mock_wrapper(wrapper, expect_pattern, rp):
    """
    Title: SDK Adapter - Master Request using Mock Wrapper

    Description:
        Verify that request() prefers the SDK's BLC_sendRawMasterRequest when
        available. The mock wrapper enqueues a deterministic response where
        data[i] = (id + i) & 0xFF, allowing predictable assertions.

    Requirements: REQ-001

    Test Steps:
        1. Construct BabyLinInterface with injected mock wrapper
        2. Connect (mock open/initialize)
        3. Issue a master request for a specific ID and length
        4. Receive the response frame
        5. Validate ID and deterministic payload pattern

    Expected Result:
        - Response frame ID matches request ID
        - Response data length matches requested length
        - Response data follows deterministic pattern
    """
    # Step 1-2: Initialize mock-backed adapter
    lin = BabyLinInterface(wrapper_module=wrapper)
    rp("wrapper", getattr(wrapper, "__name__", str(wrapper)))
    lin.connect()
    try:
        # Step 3: Request 4 bytes for ID 0x22
        req_id = 0x22
        length = 4
        rp("req_id", f"0x{req_id:02X}")
        rp("req_len", length)
        rx = lin.request(id=req_id, length=length, timeout=0.1)

        # Step 4-5: Validate response
        assert rx is not None, "Expected a response from mock master request"
        assert rx.id == req_id
        if expect_pattern:
            # length-signature mock returns deterministic pattern
            expected = bytes(((req_id + i) & 0xFF) for i in range(length))
            rp("expected_data", list(expected))
            rp("rx_data", list(rx.data))
            assert rx.data == expected
        else:
            # bytes-only mock returns exactly the bytes we sent (zeros of requested length)
            expected = bytes([0] * length)
            rp("expected_data", list(expected))
            rp("rx_data", list(rx.data))
            assert rx.data == expected
    finally:
        lin.disconnect()
