"""Test OpenZWave Websocket API."""
from unittest.mock import patch

from openzwavemqtt.const import (
    ATTR_CODE_SLOT,
    ATTR_LABEL,
    ATTR_OPTIONS,
    ATTR_POSITION,
    ATTR_VALUE,
    ValueType,
)

from homeassistant.components.ozw.const import ATTR_CONFIG_PARAMETER
from homeassistant.components.ozw.lock import ATTR_USERCODE
from homeassistant.components.ozw.websocket_api import (
    ATTR_IS_AWAKE,
    ATTR_IS_BEAMING,
    ATTR_IS_FAILED,
    ATTR_IS_FLIRS,
    ATTR_IS_ROUTING,
    ATTR_IS_SECURITYV1,
    ATTR_IS_ZWAVE_PLUS,
    ATTR_NEIGHBORS,
    ATTR_NODE_BASIC_STRING,
    ATTR_NODE_BAUD_RATE,
    ATTR_NODE_GENERIC_STRING,
    ATTR_NODE_QUERY_STAGE,
    ATTR_NODE_SPECIFIC_STRING,
    ID,
    NODE_ID,
    OZW_INSTANCE,
    PARAMETER,
    SCHEMA,
    TYPE,
    VALUE,
)
from homeassistant.components.websocket_api.const import (
    ERR_INVALID_FORMAT,
    ERR_NOT_FOUND,
    ERR_NOT_SUPPORTED,
)

from .common import MQTTMessage, setup_ozw


async def test_websocket_api(hass, generic_data, hass_ws_client):
    """Test the ozw websocket api."""
    await setup_ozw(hass, fixture=generic_data)
    client = await hass_ws_client(hass)

    # Test instance list
    await client.send_json({ID: 4, TYPE: "ozw/get_instances"})
    msg = await client.receive_json()
    assert len(msg["result"]) == 1
    result = msg["result"][0]
    assert result[OZW_INSTANCE] == 1
    assert result["Status"] == "driverAllNodesQueried"
    assert result["OpenZWave_Version"] == "1.6.1008"

    # Test network status
    await client.send_json({ID: 5, TYPE: "ozw/network_status"})
    msg = await client.receive_json()
    result = msg["result"]

    assert result["Status"] == "driverAllNodesQueried"
    assert result[OZW_INSTANCE] == 1

    # Test node status
    await client.send_json({ID: 6, TYPE: "ozw/node_status", NODE_ID: 32})
    msg = await client.receive_json()
    result = msg["result"]

    assert result[OZW_INSTANCE] == 1
    assert result[NODE_ID] == 32
    assert result[ATTR_NODE_QUERY_STAGE] == "Complete"
    assert result[ATTR_IS_ZWAVE_PLUS]
    assert result[ATTR_IS_AWAKE]
    assert not result[ATTR_IS_FAILED]
    assert result[ATTR_NODE_BAUD_RATE] == 100000
    assert result[ATTR_IS_BEAMING]
    assert not result[ATTR_IS_FLIRS]
    assert result[ATTR_IS_ROUTING]
    assert not result[ATTR_IS_SECURITYV1]
    assert result[ATTR_NODE_BASIC_STRING] == "Routing Slave"
    assert result[ATTR_NODE_GENERIC_STRING] == "Binary Switch"
    assert result[ATTR_NODE_SPECIFIC_STRING] == "Binary Power Switch"
    assert result[ATTR_NEIGHBORS] == [1, 33, 36, 37, 39]

    await client.send_json({ID: 7, TYPE: "ozw/node_status", NODE_ID: 999})
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND

    # Test node statistics
    await client.send_json({ID: 8, TYPE: "ozw/node_statistics", NODE_ID: 39})
    msg = await client.receive_json()
    result = msg["result"]

    assert result[OZW_INSTANCE] == 1
    assert result[NODE_ID] == 39
    assert result["send_count"] == 57
    assert result["sent_failed"] == 0
    assert result["retries"] == 1
    assert result["last_request_rtt"] == 26
    assert result["last_response_rtt"] == 38
    assert result["average_request_rtt"] == 29
    assert result["average_response_rtt"] == 37
    assert result["received_packets"] == 3594
    assert result["received_dup_packets"] == 12
    assert result["received_unsolicited"] == 3546

    # Test node metadata
    await client.send_json({ID: 9, TYPE: "ozw/node_metadata", NODE_ID: 39})
    msg = await client.receive_json()
    result = msg["result"]
    assert result["metadata"]["ProductPic"] == "images/aeotec/zwa002.png"

    await client.send_json({ID: 10, TYPE: "ozw/node_metadata", NODE_ID: 999})
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND

    # Test network statistics
    await client.send_json({ID: 11, TYPE: "ozw/network_statistics"})
    msg = await client.receive_json()
    result = msg["result"]
    assert result["readCnt"] == 92220
    assert result[OZW_INSTANCE] == 1
    assert result["node_count"] == 5

    # Test get nodes
    await client.send_json({ID: 12, TYPE: "ozw/get_nodes"})
    msg = await client.receive_json()
    result = msg["result"]
    assert len(result) == 5
    assert result[2][ATTR_IS_AWAKE]
    assert not result[1][ATTR_IS_FAILED]

    # Test get config parameters
    await client.send_json({ID: 13, TYPE: "ozw/get_config_parameters", NODE_ID: 39})
    msg = await client.receive_json()
    result = msg["result"]
    assert len(result) == 8
    for config_param in result:
        assert config_param["type"] in (
            ValueType.LIST.value,
            ValueType.BOOL.value,
            ValueType.INT.value,
            ValueType.BYTE.value,
            ValueType.SHORT.value,
            ValueType.BITSET.value,
        )

    # Test set config parameter
    config_param = result[0]
    current_val = config_param[ATTR_VALUE]
    new_val = next(
        option[0]
        for option in config_param[SCHEMA][0][ATTR_OPTIONS]
        if option[0] != current_val
    )
    new_label = next(
        option[1]
        for option in config_param[SCHEMA][0][ATTR_OPTIONS]
        if option[1] != current_val and option[0] != new_val
    )
    await client.send_json(
        {
            ID: 14,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: config_param[ATTR_CONFIG_PARAMETER],
            VALUE: new_val,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]
    await client.send_json(
        {
            ID: 15,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: config_param[ATTR_CONFIG_PARAMETER],
            VALUE: new_label,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]

    # Test OZW Instance not found error
    await client.send_json(
        {ID: 16, TYPE: "ozw/get_config_parameters", OZW_INSTANCE: 999, NODE_ID: 1}
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND

    # Test OZW Node not found error
    await client.send_json(
        {
            ID: 18,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 999,
            PARAMETER: 0,
            VALUE: "test",
        }
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND

    # Test parameter not found
    await client.send_json(
        {
            ID: 19,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: 45,
            VALUE: "test",
        }
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND

    # Test list value not found
    await client.send_json(
        {
            ID: 20,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: config_param[ATTR_CONFIG_PARAMETER],
            VALUE: "test",
        }
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND

    # Test value type invalid
    await client.send_json(
        {
            ID: 21,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: 3,
            VALUE: 0,
        }
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_SUPPORTED

    # Test invalid bitset format
    await client.send_json(
        {
            ID: 22,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: 3,
            VALUE: {ATTR_POSITION: 1, ATTR_VALUE: True, ATTR_LABEL: "test"},
        }
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_INVALID_FORMAT

    # Test valid bitset format passes validation
    await client.send_json(
        {
            ID: 23,
            TYPE: "ozw/set_config_parameter",
            NODE_ID: 39,
            PARAMETER: 10000,
            VALUE: {ATTR_POSITION: 1, ATTR_VALUE: True},
        }
    )
    msg = await client.receive_json()
    result = msg["error"]
    assert result["code"] == ERR_NOT_FOUND


async def test_ws_locks(hass, lock_data, hass_ws_client):
    """Test lock websocket apis."""
    await setup_ozw(hass, fixture=lock_data)
    client = await hass_ws_client(hass)

    await client.send_json(
        {
            ID: 1,
            TYPE: "ozw/get_code_slots",
            NODE_ID: 10,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]

    await client.send_json(
        {
            ID: 2,
            TYPE: "ozw/set_usercode",
            NODE_ID: 10,
            ATTR_CODE_SLOT: 1,
            ATTR_USERCODE: "1234",
        }
    )
    msg = await client.receive_json()
    assert msg["success"]

    await client.send_json(
        {
            ID: 3,
            TYPE: "ozw/clear_usercode",
            NODE_ID: 10,
            ATTR_CODE_SLOT: 1,
        }
    )
    msg = await client.receive_json()
    assert msg["success"]


async def test_refresh_node(hass, generic_data, sent_messages, hass_ws_client):
    """Test the ozw refresh node api."""
    receive_message = await setup_ozw(hass, fixture=generic_data)
    client = await hass_ws_client(hass)

    # Send the refresh_node_info command
    await client.send_json({ID: 9, TYPE: "ozw/refresh_node_info", NODE_ID: 39})
    msg = await client.receive_json()

    assert len(sent_messages) == 1
    assert msg["success"]

    # Receive a mock status update from OZW
    message = MQTTMessage(
        topic="OpenZWave/1/node/39/",
        payload={"NodeID": 39, "NodeQueryStage": "initializing"},
    )
    message.encode()
    receive_message(message)

    # Verify we got expected data on the websocket
    msg = await client.receive_json()
    result = msg["event"]
    assert result["type"] == "node_updated"
    assert result["node_query_stage"] == "initializing"

    # Send another mock status update from OZW
    message = MQTTMessage(
        topic="OpenZWave/1/node/39/",
        payload={"NodeID": 39, "NodeQueryStage": "versions"},
    )
    message.encode()
    receive_message(message)

    # Send a mock status update for a different node
    message = MQTTMessage(
        topic="OpenZWave/1/node/35/",
        payload={"NodeID": 35, "NodeQueryStage": "fake_shouldnt_be_received"},
    )
    message.encode()
    receive_message(message)

    # Verify we received the message for node 39 but not for node 35
    msg = await client.receive_json()
    result = msg["event"]
    assert result["type"] == "node_updated"
    assert result["node_query_stage"] == "versions"


async def test_refresh_node_unsubscribe(hass, generic_data, hass_ws_client):
    """Test unsubscribing the ozw refresh node api."""
    await setup_ozw(hass, fixture=generic_data)
    client = await hass_ws_client(hass)

    with patch("openzwavemqtt.OZWOptions.listen") as mock_listen:
        # Send the refresh_node_info command
        await client.send_json({ID: 9, TYPE: "ozw/refresh_node_info", NODE_ID: 39})
        await client.receive_json()

        # Send the unsubscribe command
        await client.send_json({ID: 10, TYPE: "unsubscribe_events", "subscription": 9})
        await client.receive_json()

        assert mock_listen.return_value.called