import pytest

from ecu_framework.lin.base import LinFrame
from ecu_framework.lin.mock import MockBabyLinInterface


@pytest.fixture(scope="module")
def lin():
    """Module-local override: these tests are explicitly mock-only and must
    not depend on whatever real-hardware interface the central config selects."""
    iface = MockBabyLinInterface(bitrate=19200, channel=0)
    iface.connect()
    yield iface
    iface.disconnect()


class TestMockLinInterface:
    """Test suite validating the pure-Python mock LIN interface behavior.

    Coverage goals:
    - REQ-001: Echo loopback for local testing (send -> receive same frame)
    - REQ-002: Deterministic master request responses (no randomness)
    - REQ-003: Frame ID filtering in receive()
    - REQ-004: Graceful handling of timeout when no frame is available

    Notes:
    - These tests run entirely without hardware and should be fast and stable.
    - The injected mock interface enqueues frames on transmit to emulate a bus.
    - Deterministic responses allow exact byte-for-byte assertions.
    """

    @pytest.mark.smoke
    @pytest.mark.req_001
    @pytest.mark.req_003
    def test_mock_send_receive_echo(self, lin, rp):
        """
        Title: Mock LIN Interface - Send/Receive Echo Test
        
        Description:
            Validates that the mock LIN interface correctly echoes frames sent on the bus,
            enabling loopback testing without hardware dependencies.
            
        Requirements: REQ-001, REQ-003
        
        Test Steps:
            1. Create a LIN frame with specific ID and data payload
            2. Send the frame via the mock interface
            3. Attempt to receive the echoed frame with ID filtering
            4. Verify the received frame matches the transmitted frame exactly
            
        Expected Result:
            - Frame is successfully echoed by mock interface
            - Received frame ID matches transmitted frame ID (0x12)
            - Received frame data payload matches transmitted data [1, 2, 3]
        """
        # Step 1: Create test frame with known ID and payload
        test_frame = LinFrame(id=0x12, data=bytes([1, 2, 3]))
        rp("lin_type", "mock")
        rp("tx_id", f"0x{test_frame.id:02X}")
        rp("tx_data", list(test_frame.data))
        
        # Step 2: Transmit frame via mock interface (mock will enqueue to RX)
        lin.send(test_frame)
        
        # Step 3: Receive echoed frame with ID filtering and timeout
        received_frame = lin.receive(id=0x12, timeout=0.5)
        rp("rx_present", received_frame is not None)
        if received_frame is not None:
            rp("rx_id", f"0x{received_frame.id:02X}")
            rp("rx_data", list(received_frame.data))
        
        # Step 4: Validate echo functionality and payload integrity
        assert received_frame is not None, "Mock interface should echo transmitted frames"
        assert received_frame.id == test_frame.id, f"Expected ID {test_frame.id:#x}, got {received_frame.id:#x}"
        assert received_frame.data == test_frame.data, f"Expected data {test_frame.data!r}, got {received_frame.data!r}"

    @pytest.mark.smoke
    @pytest.mark.req_002
    def test_mock_request_synthesized_response(self, lin, rp):
        """
        Title: Mock LIN Interface - Master Request Response Test
        
        Description:
            Validates that the mock interface synthesizes deterministic responses
            for master request operations, simulating slave node behavior.
            
        Requirements: REQ-002
        
        Test Steps:
            1. Issue a master request for specific frame ID and data length
            2. Verify mock interface generates a response frame
            3. Validate response frame ID matches request ID
            4. Verify response data length matches requested length
            5. Confirm response data is deterministic (not random)
            
        Expected Result:
            - Mock interface generates response within timeout period
            - Response frame ID matches request ID (0x21)
            - Response data length equals requested length (4 bytes)
            - Response data follows deterministic pattern: [id+0, id+1, id+2, id+3]
        """
        # Step 1: Issue master request with specific parameters
        request_id = 0x21
        requested_length = 4
        
        # Step 2: Execute request operation; mock synthesizes deterministic bytes
        rp("lin_type", "mock")
        rp("req_id", f"0x{request_id:02X}")
        rp("req_len", requested_length)
        response_frame = lin.request(id=request_id, length=requested_length, timeout=0.5)
        
        # Step 3: Validate response generation
        assert response_frame is not None, "Mock interface should generate response for master requests"
        
        # Step 4: Verify response frame properties (ID and length)
        assert response_frame.id == request_id, f"Response ID {response_frame.id:#x} should match request ID {request_id:#x}"
        assert len(response_frame.data) == requested_length, f"Response length {len(response_frame.data)} should match requested length {requested_length}"
        
        # Step 5: Validate deterministic response pattern
        expected_data = bytes((request_id + i) & 0xFF for i in range(requested_length))
        rp("rx_data", list(response_frame.data) if response_frame else None)
        rp("expected_data", list(expected_data))
        assert response_frame.data == expected_data, f"Response data {response_frame.data!r} should follow deterministic pattern {expected_data!r}"

    @pytest.mark.smoke
    @pytest.mark.req_004
    def test_mock_receive_timeout_behavior(self, lin, rp):
        """
        Title: Mock LIN Interface - Receive Timeout Test
        
        Description:
            Validates that the mock interface properly handles timeout scenarios
            when no matching frames are available for reception.
            
        Requirements: REQ-004
        
        Test Steps:
            1. Attempt to receive a frame with non-existent ID
            2. Use short timeout to avoid blocking test execution
            3. Verify timeout behavior returns None rather than blocking indefinitely
            
        Expected Result:
            - Receive operation returns None when no matching frames available
            - Operation completes within specified timeout period
            - No exceptions or errors during timeout scenario
        """
        # Step 1: Attempt to receive frame with ID that hasn't been transmitted
        non_existent_id = 0xFF
        short_timeout = 0.1  # 100ms timeout
        
        # Step 2: Execute receive with timeout (should return None quickly)
        rp("lin_type", "mock")
        rp("rx_id", f"0x{non_existent_id:02X}")
        rp("timeout_s", short_timeout)
        result = lin.receive(id=non_existent_id, timeout=short_timeout)
        rp("rx_present", result is not None)
        
        # Step 3: Verify proper timeout behavior (no exceptions, returns None)
        assert result is None, "Receive operation should return None when no matching frames available"

    @pytest.mark.boundary
    @pytest.mark.req_001
    @pytest.mark.req_003
    @pytest.mark.parametrize("frame_id,data_payload", [
        (0x01, bytes([0x55])),
        (0x3F, bytes([0xAA, 0x55])),
        (0x20, bytes([0x01, 0x02, 0x03, 0x04, 0x05])),
        (0x15, bytes([0xFF, 0x00, 0xCC, 0x33, 0xF0, 0x0F, 0xA5, 0x5A])),
    ])
    def test_mock_frame_validation_boundaries(self, lin, rp, frame_id, data_payload):
        """
        Title: Mock LIN Interface - Frame Validation Boundaries Test
        
        Description:
            Validates mock interface handling of various frame configurations
            including boundary conditions for frame IDs and data lengths.
            
        Requirements: REQ-001, REQ-003
        
        Test Steps:
            1. Test various valid frame ID values (0x01 to 0x3F)
            2. Test different data payload lengths (1 to 8 bytes)
            3. Verify proper echo behavior for all valid combinations
            
        Expected Result:
            - All valid frame configurations are properly echoed
            - Frame ID and data integrity preserved across echo operation
        """
        # Step 1: Create frame with parameterized values
        test_frame = LinFrame(id=frame_id, data=data_payload)
        rp("lin_type", "mock")
        rp("tx_id", f"0x{frame_id:02X}")
        rp("tx_len", len(data_payload))
        
        # Step 2: Send and receive frame
        lin.send(test_frame)
        received_frame = lin.receive(id=frame_id, timeout=0.5)
        
        # Step 3: Validate frame integrity across IDs and payload sizes
        assert received_frame is not None, f"Frame with ID {frame_id:#x} should be echoed"
        assert received_frame.id == frame_id, f"Frame ID should be preserved: expected {frame_id:#x}"
        assert received_frame.data == data_payload, f"Frame data should be preserved for ID {frame_id:#x}"
