"""Comprehensive UDS tests ported from deprecated Swift ECUmulator tests."""
from __future__ import annotations

import pytest

from pymulator.core.automotive import UdsNegativeResponse
from pymulator.core.uds import NegativeResponseCode, ServiceId

from .conftest import load_handler, make_message


def test_clear_diagnostic_information_removes_stored_dtcs():
    """Verify clear DTCs removes stored DTCs."""
    session = load_handler("uds.json")

    dtc_before = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_dtc_information, 0x02, 0xFF])
    )
    assert dtc_before == [0x59, 0x02, 0x08, 1, 2, 3, 0x08]

    clear_resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.clear_diagnostic_information, 0xFF, 0xFF, 0xFF])
    )
    assert clear_resp == [0x54]

    dtc_after = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_dtc_information, 0x02, 0xFF])
    )
    assert dtc_after == [0x59, 0x02, 0x00]


def test_communication_control_acknowledges_request():
    """Verify communication control returns positive response."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.communication_control, 0x00, 0x01])
    )
    assert resp == [0x68]


def test_diagnostic_session_control_returns_timing_parameters():
    """Verify session control returns proper timing parameters."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )
    assert resp[0] == 0x50
    assert resp[1] == 0x02
    assert len(resp) == 6


def test_control_dtc_setting_responds_positive():
    """Verify DTC setting control returns positive response."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.control_dtc_setting, 0x01])
    )
    assert resp == [0xC5]


def test_dynamically_define_data_identifier_by_identifier():
    """Test defining a dynamic DID from an existing identifier."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(
            session.arbitration_id,
            [ServiceId.dynamically_define_data_identifier, 0x01, 0xF3, 0x00, 0xF2, 0x00, 0x01, 0x01]
        )
    )
    assert resp == [0x6C, 0x01, 0xF3, 0x00]

    read = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF3, 0x00])
    )
    assert read == [0x62, 0xF3, 0x00, 0xDE]


def test_dynamically_define_data_identifier_by_identifier_multiple_records():
    """Test defining a dynamic DID with multiple source records from different identifiers."""
    session = load_handler("uds.json")
    request = [
        ServiceId.dynamically_define_data_identifier,
        0x01, 0xF3, 0x02,
        0xF2, 0x00, 0x01, 0x02,
        0xF1, 0x87, 0x01, 0x02
    ]
    resp = session.handle_request(make_message(session.arbitration_id, request))
    assert resp == [0x6C, 0x01, 0xF3, 0x02]

    read = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF3, 0x02])
    )
    assert read == [0x62, 0xF3, 0x02, 0xDE, 0xAD, 0x01, 0x02]


def test_dynamically_define_data_identifier_by_memory_address():
    """Test defining a dynamic DID from memory address."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(
            session.arbitration_id,
            [ServiceId.dynamically_define_data_identifier, 0x02, 0xF3, 0x01, 0x22, 0x10, 0x00, 0x00, 0x04]
        )
    )
    assert resp == [0x6C, 0x02, 0xF3, 0x01]

    read = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF3, 0x01])
    )
    assert read[0:3] == [0x62, 0xF3, 0x01]
    assert len(read) == 7


def test_clearing_dynamic_data_identifier_removes_definition():
    """Test clearing a dynamic DID removes it."""
    session = load_handler("uds.json")

    session.handle_request(
        make_message(
            session.arbitration_id,
            [ServiceId.dynamically_define_data_identifier, 0x01, 0xF3, 0x00, 0xF2, 0x00, 0x01, 0x01]
        )
    )

    cleared = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.dynamically_define_data_identifier, 0x03, 0xF3, 0x00])
    )
    assert cleared == [0x6C, 0x03, 0xF3, 0x00]

    with pytest.raises(UdsNegativeResponse) as exc_info:
        session.handle_request(
            make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF3, 0x00])
        )
    assert exc_info.value.code == NegativeResponseCode.request_out_of_range


def test_ecu_reset_responds_with_reset_type():
    """Verify ECU reset returns correct response."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.ecu_reset, 0x01])
    )
    assert resp == [0x51, 0x01]


def test_read_data_by_identifier_returns_content():
    """Verify reading a DID returns its content."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF1, 0x90])
    )
    assert resp[:3] == [0x62, 0xF1, 0x90]
    assert bytes(resp[3:]).decode("utf-8") == "TESTVIN123456789"


def test_read_data_by_identifier_returns_multiple_identifiers():
    """Verify reading multiple DIDs in one request."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF1, 0x90, 0xF2, 0x00])
    )
    expected = [0x62, 0xF1, 0x90]
    expected += list("TESTVIN123456789".encode("utf-8"))
    expected += [0xF2, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]
    assert resp == expected


def test_write_data_by_identifier_honors_security_restrictions():
    """Verify write DID respects security level requirements."""
    session = load_handler("uds.json")

    with pytest.raises(UdsNegativeResponse) as exc_info:
        session.handle_request(
            make_message(session.arbitration_id, [ServiceId.write_data_by_identifier, 0xF1, 0xA0, 0x12, 0x34])
        )
    assert exc_info.value.code == NegativeResponseCode.security_access_denied

    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x01])
    )
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x02, 0xCC, 0xDD])
    )

    write_resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.write_data_by_identifier, 0xF1, 0xA0, 0x12, 0x34])
    )
    assert write_resp == [0x6E, 0xF1, 0xA0]

    verify = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF1, 0xA0])
    )
    assert verify == [0x62, 0xF1, 0xA0, 0x12, 0x34]


def test_write_data_by_identifier_without_protection_succeeds():
    """Verify unprotected DID can be written without security."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.write_data_by_identifier, 0xF1, 0xA1, 0xAA, 0x55])
    )
    assert resp == [0x6E, 0xF1, 0xA1]

    verify = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_data_by_identifier, 0xF1, 0xA1])
    )
    assert verify == [0x62, 0xF1, 0xA1, 0xAA, 0x55]


def test_read_dtc_information_reports_status_mask_and_dtc():
    """Verify DTC information includes status and DTC bytes."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_dtc_information, 0x02, 0xFF])
    )
    assert resp == [0x59, 0x02, 0x08, 1, 2, 3, 0x08]


def test_read_memory_by_address_returns_requested_bytes():
    """Verify reading memory returns the requested bytes."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.read_memory_by_address, 0x22, 0x10, 0x00, 0x00, 0x10])
    )
    assert resp[0] == 0x63
    payload = resp[1:]
    assert len(payload) == 16


def test_request_download_transfer_data_and_exit_cycle_completes():
    """Verify complete download/transfer/exit cycle."""
    session = load_handler("uds.json")
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )

    download = session.handle_request(
        make_message(
            session.arbitration_id,
            [ServiceId.request_download, 0x00, 0x22, 0x10, 0x00, 0x00, 0x10]
        )
    )
    assert download == [0x74, 0x20, 0x00, 0x08]

    block1 = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.transfer_data, 0x01] + list(range(8)))
    )
    assert block1 == [0x76, 0x01]

    block2 = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.transfer_data, 0x02] + list(range(8, 16)))
    )
    assert block2 == [0x76, 0x02]

    exit_resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.request_transfer_exit])
    )
    assert exit_resp == [0x77]


def test_request_upload_transfer_data_and_exit_cycle_retrieves_memory():
    """Verify complete upload/transfer/exit cycle."""
    session = load_handler("uds.json")
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )

    upload = session.handle_request(
        make_message(
            session.arbitration_id,
            [ServiceId.request_upload, 0x00, 0x22, 0x10, 0x00, 0x00, 0x10]
        )
    )
    assert upload == [0x75, 0x20, 0x00, 0x08]

    block1 = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.transfer_data, 0x01])
    )
    assert block1[0:2] == [0x76, 0x01]
    assert len(block1) == 10

    block2 = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.transfer_data, 0x02])
    )
    assert block2[0:2] == [0x76, 0x02]
    assert len(block2) == 10

    exit_resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.request_transfer_exit])
    )
    assert exit_resp == [0x77]


def test_routine_control_start_stop_and_results():
    """Verify routine control start/stop/results cycle."""
    session = load_handler("uds.json")

    start = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.routine_control, 0x01, 0x12, 0x34, 0x01])
    )
    assert start == [0x71, 0x01, 0x12, 0x34, 0x90, 0x01]

    stop = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.routine_control, 0x02, 0x12, 0x34, 0x01])
    )
    assert stop == [0x71, 0x02, 0x12, 0x34, 0x90, 0x02]

    results = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.routine_control, 0x03, 0x12, 0x34, 0x01])
    )
    assert results == [0x71, 0x03, 0x12, 0x34, 0x90, 0x03]


def test_security_access_seed_and_key_unlocks_session():
    """Verify security access seed/key flow unlocks the session."""
    session = load_handler("uds.json")
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )

    seed_resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x01])
    )
    assert seed_resp == [0x67, 0x01, 0xAA, 0xBB]

    key_resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x02, 0xCC, 0xDD])
    )
    assert key_resp == [0x67, 0x02]


def test_security_access_wrong_key_rejected():
    """Verify wrong security key is rejected."""
    session = load_handler("uds.json")
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x01])
    )

    with pytest.raises(UdsNegativeResponse) as exc_info:
        session.handle_request(
            make_message(session.arbitration_id, [ServiceId.security_access, 0x02, 0xFF, 0xFF])
        )
    assert exc_info.value.code == NegativeResponseCode.invalid_key


def test_tester_present_responds_when_requested():
    """Verify tester present returns positive response."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.tester_present, 0x00])
    )
    assert resp == [0x7E, 0x00]


def test_tester_present_suppress_response():
    """Verify tester present with suppress bit does not respond."""
    session = load_handler("uds.json")
    resp = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.tester_present, 0x80])
    )
    assert resp is None or resp == []


def test_negative_response_for_unsupported_routine():
    """Verify unsupported routine returns negative response."""
    session = load_handler("uds.json")
    with pytest.raises(UdsNegativeResponse) as exc_info:
        session.handle_request(
            make_message(session.arbitration_id, [ServiceId.routine_control, 0x01, 0x99, 0x99])
        )
    assert exc_info.value.code == NegativeResponseCode.request_out_of_range


def test_multiple_security_levels():
    """Verify multiple security levels can be accessed."""
    session = load_handler("uds.json")
    session.handle_request(
        make_message(session.arbitration_id, [ServiceId.diagnostic_session_control, 0x02])
    )

    seed1 = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x03])
    )
    assert seed1 == [0x67, 0x03, 0x11, 0x22, 0x33, 0x44]

    key1 = session.handle_request(
        make_message(session.arbitration_id, [ServiceId.security_access, 0x04, 0x11, 0x22, 0x33, 0x44])
    )
    assert key1 == [0x67, 0x04]
