import os

import pytest

from matterlab_relays import R421B16Relay


PORT = "/dev/ttyUSB0" if os.name == "posix" else "COM1"


@pytest.fixture
def relay_fixture(mock_serial):
    return R421B16Relay(com_port=PORT, address=1, channel=1)


def test_initialization_defaults(mock_serial, relay_fixture):
    assert isinstance(relay_fixture, R421B16Relay)
    assert relay_fixture.address == 1
    assert relay_fixture.channel == 1
    assert relay_fixture.device._connection is mock_serial


def test_constructor_rejects_invalid_channel(mock_serial):
    with pytest.raises(AssertionError, match="R421B16 relay channel must be in \\[1,16\\]!"):
        R421B16Relay(com_port=PORT, address=1, channel=17)


def test_crc16_matches_known_command():
    command = bytes.fromhex("010300010001")

    assert R421B16Relay._cal_modbus_CRC16(command).hex() == "010300010001d5ca"


def test_generate_query_and_execute_commands(relay_fixture):
    assert relay_fixture._generate_query_command(1).hex() == "010300010001d5ca"
    assert relay_fixture._generate_execute_command(1, True).hex() == "010600010100d99a"
    assert relay_fixture._generate_execute_command(1, False).hex() == "010600010200d96a"


def test_set_relay_writes_execution_command(relay_fixture, mocker):
    mocker.patch("matterlab_relays.r421b16_relay.time.sleep")
    write_spy = mocker.patch.object(relay_fixture, "write")

    relay_fixture.set_relay(channel=1, open_channel=True)

    write_spy.assert_called_once_with(command=bytes.fromhex("010600010100d99a"))


def test_query_relay_parses_boolean_status(relay_fixture, mocker):
    query_spy = mocker.patch.object(relay_fixture, "query", return_value=b"\x01\x03\x02\x00\x01\x79\x84")

    assert relay_fixture.query_relay(channel=1) is True
    query_spy.assert_called_once_with(
        write_command=bytes.fromhex("010300010001d5ca"),
        num_bytes=7,
        remove_from_end=0,
        return_bytes=True,
        read_delay=0.1,
    )


def test_on_property_routes_to_query_and_set(relay_fixture, mocker):
    query_spy = mocker.patch.object(relay_fixture, "query_relay", return_value=False)
    set_spy = mocker.patch.object(relay_fixture, "set_relay")

    assert relay_fixture.on is False
    relay_fixture.on = True

    query_spy.assert_called_once_with(channel=1)
    set_spy.assert_called_once_with(channel=1, open_channel=True)
