"""Test the Z-Wave JS lock platform."""

import pytest
from zwave_js_server.const import CommandClass
from zwave_js_server.const.command_class.lock import (
    ATTR_CODE_SLOT,
    ATTR_USERCODE,
    CURRENT_MODE_PROPERTY,
)
from zwave_js_server.event import Event
from zwave_js_server.exceptions import FailedZWaveCommand
from zwave_js_server.model.node import Node, NodeStatus

from homeassistant.components.lock import (
    DOMAIN as LOCK_DOMAIN,
    SERVICE_LOCK,
    SERVICE_UNLOCK,
)
from homeassistant.components.zwave_js.const import (
    ATTR_LOCK_TIMEOUT,
    ATTR_OPERATION_TYPE,
    DOMAIN as ZWAVE_JS_DOMAIN,
)
from homeassistant.components.zwave_js.helpers import ZwaveValueMatcher
from homeassistant.components.zwave_js.lock import (
    SERVICE_CLEAR_LOCK_USERCODE,
    SERVICE_SET_LOCK_CONFIGURATION,
    SERVICE_SET_LOCK_USERCODE,
)
from homeassistant.const import (
    ATTR_ENTITY_ID,
    STATE_LOCKED,
    STATE_UNAVAILABLE,
    STATE_UNKNOWN,
    STATE_UNLOCKED,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .common import SCHLAGE_BE469_LOCK_ENTITY, replace_value_of_zwave_value


async def test_door_lock(
    hass: HomeAssistant,
    client,
    lock_schlage_be469,
    integration,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test a lock entity with door lock command class."""
    node = lock_schlage_be469
    state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)

    assert state
    assert state.state == STATE_UNLOCKED

    # Test locking
    await hass.services.async_call(
        LOCK_DOMAIN,
        SERVICE_LOCK,
        {ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY},
        blocking=True,
    )

    assert len(client.async_send_command.call_args_list) == 1
    args = client.async_send_command.call_args[0][0]
    assert args["command"] == "node.set_value"
    assert args["nodeId"] == 20
    assert args["valueId"] == {
        "commandClass": 98,
        "endpoint": 0,
        "property": "targetMode",
    }
    assert args["value"] == 255

    client.async_send_command.reset_mock()

    # Test locked update from value updated event
    event = Event(
        type="value updated",
        data={
            "source": "node",
            "event": "value updated",
            "nodeId": 20,
            "args": {
                "commandClassName": "Door Lock",
                "commandClass": 98,
                "endpoint": 0,
                "property": "currentMode",
                "newValue": 255,
                "prevValue": 0,
                "propertyName": "currentMode",
            },
        },
    )
    node.receive_event(event)

    assert hass.states.get(SCHLAGE_BE469_LOCK_ENTITY).state == STATE_LOCKED

    client.async_send_command.reset_mock()

    # Test unlocking
    await hass.services.async_call(
        LOCK_DOMAIN,
        SERVICE_UNLOCK,
        {ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY},
        blocking=True,
    )

    assert len(client.async_send_command.call_args_list) == 1
    args = client.async_send_command.call_args[0][0]
    assert args["command"] == "node.set_value"
    assert args["nodeId"] == 20
    assert args["valueId"] == {
        "commandClass": 98,
        "endpoint": 0,
        "property": "targetMode",
    }
    assert args["value"] == 0

    client.async_send_command.reset_mock()

    # Test set usercode service
    await hass.services.async_call(
        ZWAVE_JS_DOMAIN,
        SERVICE_SET_LOCK_USERCODE,
        {
            ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY,
            ATTR_CODE_SLOT: 1,
            ATTR_USERCODE: "1234",
        },
        blocking=True,
    )

    assert len(client.async_send_command.call_args_list) == 1
    args = client.async_send_command.call_args[0][0]
    assert args["command"] == "node.set_value"
    assert args["nodeId"] == 20
    assert args["valueId"] == {
        "commandClass": 99,
        "endpoint": 0,
        "property": "userCode",
        "propertyKey": 1,
    }
    assert args["value"] == "1234"

    client.async_send_command.reset_mock()

    # Test clear usercode
    await hass.services.async_call(
        ZWAVE_JS_DOMAIN,
        SERVICE_CLEAR_LOCK_USERCODE,
        {ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY, ATTR_CODE_SLOT: 1},
        blocking=True,
    )

    assert len(client.async_send_command.call_args_list) == 1
    args = client.async_send_command.call_args[0][0]
    assert args["command"] == "node.set_value"
    assert args["nodeId"] == 20
    assert args["valueId"] == {
        "commandClass": 99,
        "endpoint": 0,
        "property": "userIdStatus",
        "propertyKey": 1,
    }
    assert args["value"] == 0

    client.async_send_command.reset_mock()

    # Test set configuration
    client.async_send_command.return_value = {
        "response": {"status": 1, "remainingDuration": "default"}
    }
    caplog.clear()
    await hass.services.async_call(
        ZWAVE_JS_DOMAIN,
        SERVICE_SET_LOCK_CONFIGURATION,
        {
            ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY,
            ATTR_OPERATION_TYPE: "timed",
            ATTR_LOCK_TIMEOUT: 1,
        },
        blocking=True,
    )

    assert len(client.async_send_command.call_args_list) == 1
    args = client.async_send_command.call_args[0][0]
    assert args["command"] == "endpoint.invoke_cc_api"
    assert args["nodeId"] == 20
    assert args["endpoint"] == 0
    assert args["args"] == [
        {
            "insideHandlesCanOpenDoorConfiguration": [True, True, True, True],
            "operationType": 2,
            "outsideHandlesCanOpenDoorConfiguration": [True, True, True, True],
        }
    ]
    assert args["commandClass"] == 98
    assert args["methodName"] == "setConfiguration"
    assert "Result status" in caplog.text
    assert "remaining duration" in caplog.text
    assert "setting lock configuration" in caplog.text

    client.async_send_command.reset_mock()
    client.async_send_command_no_wait.reset_mock()
    caplog.clear()

    # Put node to sleep and validate that we don't wait for a return or log anything
    event = Event(
        "sleep",
        {
            "source": "node",
            "event": "sleep",
            "nodeId": node.node_id,
        },
    )
    node.receive_event(event)

    await hass.services.async_call(
        ZWAVE_JS_DOMAIN,
        SERVICE_SET_LOCK_CONFIGURATION,
        {
            ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY,
            ATTR_OPERATION_TYPE: "timed",
            ATTR_LOCK_TIMEOUT: 1,
        },
        blocking=True,
    )

    assert len(client.async_send_command.call_args_list) == 0
    assert len(client.async_send_command_no_wait.call_args_list) == 1
    args = client.async_send_command_no_wait.call_args[0][0]
    assert args["command"] == "endpoint.invoke_cc_api"
    assert args["nodeId"] == 20
    assert args["endpoint"] == 0
    assert args["args"] == [
        {
            "insideHandlesCanOpenDoorConfiguration": [True, True, True, True],
            "operationType": 2,
            "outsideHandlesCanOpenDoorConfiguration": [True, True, True, True],
        }
    ]
    assert args["commandClass"] == 98
    assert args["methodName"] == "setConfiguration"
    assert "Result status" not in caplog.text
    assert "remaining duration" not in caplog.text
    assert "setting lock configuration" not in caplog.text

    # Mark node as alive
    event = Event(
        "alive",
        {
            "source": "node",
            "event": "alive",
            "nodeId": node.node_id,
        },
    )
    node.receive_event(event)

    client.async_send_command.side_effect = FailedZWaveCommand("test", 1, "test")
    # Test set usercode service error handling
    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            ZWAVE_JS_DOMAIN,
            SERVICE_SET_LOCK_USERCODE,
            {
                ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY,
                ATTR_CODE_SLOT: 1,
                ATTR_USERCODE: "1234",
            },
            blocking=True,
        )

    # Test clear usercode service error handling
    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            ZWAVE_JS_DOMAIN,
            SERVICE_CLEAR_LOCK_USERCODE,
            {ATTR_ENTITY_ID: SCHLAGE_BE469_LOCK_ENTITY, ATTR_CODE_SLOT: 1},
            blocking=True,
        )

    client.async_send_command.reset_mock()

    event = Event(
        type="dead",
        data={
            "source": "node",
            "event": "dead",
            "nodeId": 20,
        },
    )
    node.receive_event(event)

    assert node.status == NodeStatus.DEAD
    assert hass.states.get(SCHLAGE_BE469_LOCK_ENTITY).state == STATE_UNAVAILABLE


async def test_only_one_lock(
    hass: HomeAssistant, client, lock_home_connect_620, integration
) -> None:
    """Test node with both Door Lock and Lock CC values only gets one lock entity."""
    assert len(hass.states.async_entity_ids("lock")) == 1


async def test_door_lock_no_value(
    hass: HomeAssistant, client, lock_schlage_be469_state, integration
) -> None:
    """Test a lock entity with door lock command class that has no value for mode."""
    node_state = replace_value_of_zwave_value(
        lock_schlage_be469_state,
        [
            ZwaveValueMatcher(
                property_=CURRENT_MODE_PROPERTY,
                command_class=CommandClass.DOOR_LOCK,
            )
        ],
        None,
    )
    node = Node(client, node_state)
    client.driver.controller.emit("node added", {"node": node})
    await hass.async_block_till_done()
    state = hass.states.get(SCHLAGE_BE469_LOCK_ENTITY)
    assert state
    assert state.state == STATE_UNKNOWN