"""Test the Z-Wave JS update entities."""

import asyncio
from datetime import timedelta

from freezegun.api import FrozenDateTimeFactory
import pytest
from zwave_js_server.event import Event
from zwave_js_server.exceptions import FailedZWaveCommand
from zwave_js_server.model.node.firmware import NodeFirmwareUpdateStatus

from homeassistant.components.update import (
    ATTR_AUTO_UPDATE,
    ATTR_IN_PROGRESS,
    ATTR_INSTALLED_VERSION,
    ATTR_LATEST_VERSION,
    ATTR_RELEASE_URL,
    ATTR_SKIPPED_VERSION,
    DOMAIN as UPDATE_DOMAIN,
    SERVICE_INSTALL,
    SERVICE_SKIP,
)
from homeassistant.components.zwave_js.const import DOMAIN, SERVICE_REFRESH_VALUE
from homeassistant.components.zwave_js.helpers import get_valueless_base_unique_id
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.core import CoreState, HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_registry import async_get
from homeassistant.util import dt as dt_util

from tests.common import (
    MockConfigEntry,
    async_fire_time_changed,
    mock_restore_cache,
    mock_restore_cache_with_extra_data,
)
from tests.typing import WebSocketGenerator

UPDATE_ENTITY = "update.z_wave_thermostat_firmware"
LATEST_VERSION_FIRMWARE = {
    "version": "11.2.4",
    "changelog": "blah 2",
    "channel": "stable",
    "files": [{"target": 0, "url": "https://example2.com", "integrity": "sha2"}],
    "downgrade": True,
    "normalizedVersion": "11.2.4",
    "device": {
        "manufacturerId": 1,
        "productType": 2,
        "productId": 3,
        "firmwareVersion": "0.4.4",
        "rfRegion": 1,
    },
}
FIRMWARE_UPDATES = {
    "updates": [
        {
            "version": "10.11.1",
            "changelog": "blah 1",
            "channel": "stable",
            "files": [
                {"target": 0, "url": "https://example1.com", "integrity": "sha1"}
            ],
            "downgrade": True,
            "normalizedVersion": "10.11.1",
            "device": {
                "manufacturerId": 1,
                "productType": 2,
                "productId": 3,
                "firmwareVersion": "0.4.4",
                "rfRegion": 1,
            },
        },
        LATEST_VERSION_FIRMWARE,
        {
            "version": "11.1.5",
            "changelog": "blah 3",
            "channel": "stable",
            "files": [
                {"target": 0, "url": "https://example3.com", "integrity": "sha3"}
            ],
            "downgrade": True,
            "normalizedVersion": "11.1.5",
            "device": {
                "manufacturerId": 1,
                "productType": 2,
                "productId": 3,
                "firmwareVersion": "0.4.4",
                "rfRegion": 1,
            },
        },
        # This firmware update should never show because it's in the beta channel
        {
            "version": "999.999.999",
            "changelog": "blah 3",
            "channel": "beta",
            "files": [
                {"target": 0, "url": "https://example3.com", "integrity": "sha3"}
            ],
            "downgrade": True,
            "normalizedVersion": "999.999.999",
            "device": {
                "manufacturerId": 1,
                "productType": 2,
                "productId": 3,
                "firmwareVersion": "0.4.4",
                "rfRegion": 1,
            },
        },
    ]
}


async def test_update_entity_states(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    integration,
    caplog: pytest.LogCaptureFixture,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test update entity states."""
    ws_client = await hass_ws_client(hass)

    assert hass.states.get(UPDATE_ENTITY).state == STATE_OFF

    client.async_send_command.return_value = {"updates": []}

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF

    await ws_client.send_json(
        {
            "id": 1,
            "type": "update/release_notes",
            "entity_id": UPDATE_ENTITY,
        }
    )
    result = await ws_client.receive_json()
    assert result["result"] is None

    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=2))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_ON
    attrs = state.attributes
    assert not attrs[ATTR_AUTO_UPDATE]
    assert attrs[ATTR_INSTALLED_VERSION] == "10.7"
    assert not attrs[ATTR_IN_PROGRESS]
    assert attrs[ATTR_LATEST_VERSION] == "11.2.4"
    assert attrs[ATTR_RELEASE_URL] is None

    await ws_client.send_json(
        {
            "id": 2,
            "type": "update/release_notes",
            "entity_id": UPDATE_ENTITY,
        }
    )
    result = await ws_client.receive_json()
    assert result["result"] == "blah 2"

    # Refresh value should not be supported by this entity
    await hass.services.async_call(
        DOMAIN,
        SERVICE_REFRESH_VALUE,
        {
            ATTR_ENTITY_ID: UPDATE_ENTITY,
        },
        blocking=True,
    )
    await hass.async_block_till_done()
    assert "There is no value to refresh for this entity" in caplog.text

    client.async_send_command.return_value = {"updates": []}

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=3))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF

    # Assert a node firmware update entity is not created for the controller
    driver = client.driver
    node = driver.controller.nodes[1]
    assert node.is_controller_node
    assert (
        async_get(hass).async_get_entity_id(
            DOMAIN,
            "sensor",
            f"{get_valueless_base_unique_id(driver, node)}.firmware_update",
        )
        is None
    )

    client.async_send_command.reset_mock()


async def test_update_entity_install_raises(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    integration,
) -> None:
    """Test update entity install raises exception."""
    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    # Test failed installation by driver
    client.async_send_command.side_effect = FailedZWaveCommand("test", 12, "test")

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            UPDATE_DOMAIN,
            SERVICE_INSTALL,
            {
                ATTR_ENTITY_ID: UPDATE_ENTITY,
            },
            blocking=True,
        )


async def test_update_entity_sleep(
    hass: HomeAssistant,
    client,
    zen_31,
    integration,
) -> None:
    """Test update occurs when device is asleep after it wakes up."""
    event = Event(
        "sleep",
        data={"source": "node", "event": "sleep", "nodeId": zen_31.node_id},
    )
    zen_31.receive_event(event)
    client.async_send_command.reset_mock()

    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    # Because node is asleep we shouldn't attempt to check for firmware updates
    assert len(client.async_send_command.call_args_list) == 0

    event = Event(
        "wake up",
        data={"source": "node", "event": "wake up", "nodeId": zen_31.node_id},
    )
    zen_31.receive_event(event)
    await hass.async_block_till_done()

    # Now that the node is up we can check for updates
    assert len(client.async_send_command.call_args_list) > 0

    args = client.async_send_command.call_args_list[0][0][0]
    assert args["command"] == "controller.get_available_firmware_updates"
    assert args["nodeId"] == zen_31.node_id


async def test_update_entity_dead(
    hass: HomeAssistant,
    client,
    zen_31,
    integration,
) -> None:
    """Test update occurs when device is dead after it becomes alive."""
    event = Event(
        "dead",
        data={"source": "node", "event": "dead", "nodeId": zen_31.node_id},
    )
    zen_31.receive_event(event)
    client.async_send_command.reset_mock()

    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    # Because node is asleep we shouldn't attempt to check for firmware updates
    assert len(client.async_send_command.call_args_list) == 0

    event = Event(
        "alive",
        data={"source": "node", "event": "alive", "nodeId": zen_31.node_id},
    )
    zen_31.receive_event(event)
    await hass.async_block_till_done()

    # Now that the node is up we can check for updates
    assert len(client.async_send_command.call_args_list) > 0

    args = client.async_send_command.call_args_list[0][0][0]
    assert args["command"] == "controller.get_available_firmware_updates"
    assert args["nodeId"] == zen_31.node_id


async def test_update_entity_ha_not_running(
    hass: HomeAssistant,
    client,
    zen_31,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test update occurs only after HA is running."""
    hass.set_state(CoreState.not_running)

    client.async_send_command.return_value = {"updates": []}

    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 1

    await hass.async_start()
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 1

    # Update should be delayed by a day because HA is not running
    hass.set_state(CoreState.starting)

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 1

    hass.set_state(CoreState.running)

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 2
    args = client.async_send_command.call_args_list[1][0][0]
    assert args["command"] == "controller.get_available_firmware_updates"
    assert args["nodeId"] == zen_31.node_id


async def test_update_entity_update_failure(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    integration,
) -> None:
    """Test update entity update failed."""
    assert len(client.async_send_command.call_args_list) == 0
    client.async_send_command.side_effect = FailedZWaveCommand("test", 260, "test")

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF
    assert len(client.async_send_command.call_args_list) == 1
    args = client.async_send_command.call_args_list[0][0][0]
    assert args["command"] == "controller.get_available_firmware_updates"
    assert (
        args["nodeId"]
        == climate_radio_thermostat_ct100_plus_different_endpoints.node_id
    )


async def test_update_entity_progress(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    integration,
) -> None:
    """Test update entity progress."""
    node = climate_radio_thermostat_ct100_plus_different_endpoints
    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_ON
    attrs = state.attributes
    assert attrs[ATTR_INSTALLED_VERSION] == "10.7"
    assert attrs[ATTR_LATEST_VERSION] == "11.2.4"

    client.async_send_command.reset_mock()
    client.async_send_command.return_value = {
        "result": {"status": 2, "success": False, "reInterview": False}
    }

    # Test successful install call without a version
    install_task = hass.async_create_task(
        hass.services.async_call(
            UPDATE_DOMAIN,
            SERVICE_INSTALL,
            {
                ATTR_ENTITY_ID: UPDATE_ENTITY,
            },
            blocking=True,
        )
    )

    # Sleep so that task starts
    await asyncio.sleep(0.1)

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    attrs = state.attributes
    assert attrs[ATTR_IN_PROGRESS] is True

    event = Event(
        type="firmware update progress",
        data={
            "source": "node",
            "event": "firmware update progress",
            "nodeId": node.node_id,
            "progress": {
                "currentFile": 1,
                "totalFiles": 1,
                "sentFragments": 1,
                "totalFragments": 20,
                "progress": 5.0,
            },
        },
    )
    node.receive_event(event)

    # Validate that the progress is updated
    state = hass.states.get(UPDATE_ENTITY)
    assert state
    attrs = state.attributes
    assert attrs[ATTR_IN_PROGRESS] == 5

    event = Event(
        type="firmware update finished",
        data={
            "source": "node",
            "event": "firmware update finished",
            "nodeId": node.node_id,
            "result": {
                "status": NodeFirmwareUpdateStatus.OK_NO_RESTART,
                "success": True,
                "reInterview": False,
            },
        },
    )

    node.receive_event(event)
    await hass.async_block_till_done()

    # Validate that progress is reset and entity reflects new version
    state = hass.states.get(UPDATE_ENTITY)
    assert state
    attrs = state.attributes
    assert attrs[ATTR_IN_PROGRESS] is False
    assert attrs[ATTR_INSTALLED_VERSION] == "11.2.4"
    assert attrs[ATTR_LATEST_VERSION] == "11.2.4"
    assert state.state == STATE_OFF

    await install_task


async def test_update_entity_install_failed(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    integration,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test update entity install returns error status."""
    node = climate_radio_thermostat_ct100_plus_different_endpoints
    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_ON
    attrs = state.attributes
    assert attrs[ATTR_INSTALLED_VERSION] == "10.7"
    assert attrs[ATTR_LATEST_VERSION] == "11.2.4"

    client.async_send_command.reset_mock()
    client.async_send_command.return_value = {
        "result": {"status": 2, "success": False, "reInterview": False}
    }

    # Test install call - we expect it to finish fail
    install_task = hass.async_create_task(
        hass.services.async_call(
            UPDATE_DOMAIN,
            SERVICE_INSTALL,
            {
                ATTR_ENTITY_ID: UPDATE_ENTITY,
            },
            blocking=True,
        )
    )

    # Sleep so that task starts
    await asyncio.sleep(0.1)

    event = Event(
        type="firmware update progress",
        data={
            "source": "node",
            "event": "firmware update progress",
            "nodeId": node.node_id,
            "progress": {
                "currentFile": 1,
                "totalFiles": 1,
                "sentFragments": 1,
                "totalFragments": 20,
                "progress": 5.0,
            },
        },
    )
    node.receive_event(event)

    # Validate that the progress is updated
    state = hass.states.get(UPDATE_ENTITY)
    assert state
    attrs = state.attributes
    assert attrs[ATTR_IN_PROGRESS] == 5

    event = Event(
        type="firmware update finished",
        data={
            "source": "node",
            "event": "firmware update finished",
            "nodeId": node.node_id,
            "result": {
                "status": NodeFirmwareUpdateStatus.ERROR_TIMEOUT,
                "success": False,
                "reInterview": False,
            },
        },
    )

    node.receive_event(event)
    await hass.async_block_till_done()

    # Validate that progress is reset and entity reflects old version
    state = hass.states.get(UPDATE_ENTITY)
    assert state
    attrs = state.attributes
    assert attrs[ATTR_IN_PROGRESS] is False
    assert attrs[ATTR_INSTALLED_VERSION] == "10.7"
    assert attrs[ATTR_LATEST_VERSION] == "11.2.4"
    assert state.state == STATE_ON

    # validate that the install task failed
    with pytest.raises(HomeAssistantError):
        await install_task


async def test_update_entity_reload(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    integration,
) -> None:
    """Test update entity maintains state after reload."""
    assert hass.states.get(UPDATE_ENTITY).state == STATE_OFF

    client.async_send_command.return_value = {"updates": []}

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF

    client.async_send_command.return_value = FIRMWARE_UPDATES

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=2))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_ON
    attrs = state.attributes
    assert not attrs[ATTR_AUTO_UPDATE]
    assert attrs[ATTR_INSTALLED_VERSION] == "10.7"
    assert not attrs[ATTR_IN_PROGRESS]
    assert attrs[ATTR_LATEST_VERSION] == "11.2.4"
    assert attrs[ATTR_RELEASE_URL] is None

    await hass.services.async_call(
        UPDATE_DOMAIN,
        SERVICE_SKIP,
        {
            ATTR_ENTITY_ID: UPDATE_ENTITY,
        },
        blocking=True,
    )

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF
    assert state.attributes[ATTR_SKIPPED_VERSION] == "11.2.4"

    await hass.config_entries.async_reload(integration.entry_id)
    await hass.async_block_till_done()

    # Trigger another update and make sure the skipped version is still skipped
    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=4))
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF
    assert state.attributes[ATTR_SKIPPED_VERSION] == "11.2.4"


async def test_update_entity_delay(
    hass: HomeAssistant,
    client,
    ge_in_wall_dimmer_switch,
    zen_31,
    hass_ws_client: WebSocketGenerator,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test update occurs on a delay after HA starts."""
    client.async_send_command.reset_mock()
    client.async_send_command.return_value = {"updates": []}
    hass.set_state(CoreState.not_running)

    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 2

    await hass.async_start()
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 2

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5))
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 3
    args = client.async_send_command.call_args_list[2][0][0]
    assert args["command"] == "controller.get_available_firmware_updates"
    assert args["nodeId"] == ge_in_wall_dimmer_switch.node_id

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=10))
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 4
    args = client.async_send_command.call_args_list[3][0][0]
    assert args["command"] == "controller.get_available_firmware_updates"
    assert args["nodeId"] == zen_31.node_id


async def test_update_entity_partial_restore_data(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test update entity with partial restore data resets state."""
    mock_restore_cache(
        hass,
        [
            State(
                UPDATE_ENTITY,
                STATE_OFF,
                {
                    ATTR_INSTALLED_VERSION: "10.7",
                    ATTR_LATEST_VERSION: "11.2.4",
                    ATTR_SKIPPED_VERSION: "11.2.4",
                },
            )
        ],
    )
    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_UNKNOWN


async def test_update_entity_partial_restore_data_2(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test second scenario where update entity has partial restore data."""
    mock_restore_cache_with_extra_data(
        hass,
        [
            (
                State(
                    UPDATE_ENTITY,
                    STATE_ON,
                    {
                        ATTR_INSTALLED_VERSION: "10.7",
                        ATTR_LATEST_VERSION: "10.8",
                        ATTR_SKIPPED_VERSION: None,
                    },
                ),
                {"latest_version_firmware": None},
            )
        ],
    )
    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_UNKNOWN
    assert state.attributes[ATTR_SKIPPED_VERSION] is None
    assert state.attributes[ATTR_LATEST_VERSION] is None


async def test_update_entity_full_restore_data_skipped_version(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test update entity with full restore data (skipped version) restores state."""
    mock_restore_cache_with_extra_data(
        hass,
        [
            (
                State(
                    UPDATE_ENTITY,
                    STATE_OFF,
                    {
                        ATTR_INSTALLED_VERSION: "10.7",
                        ATTR_LATEST_VERSION: "11.2.4",
                        ATTR_SKIPPED_VERSION: "11.2.4",
                    },
                ),
                {"latest_version_firmware": LATEST_VERSION_FIRMWARE},
            )
        ],
    )
    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF
    assert state.attributes[ATTR_SKIPPED_VERSION] == "11.2.4"
    assert state.attributes[ATTR_LATEST_VERSION] == "11.2.4"


async def test_update_entity_full_restore_data_update_available(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test update entity with full restore data (update available) restores state."""
    mock_restore_cache_with_extra_data(
        hass,
        [
            (
                State(
                    UPDATE_ENTITY,
                    STATE_OFF,
                    {
                        ATTR_INSTALLED_VERSION: "10.7",
                        ATTR_LATEST_VERSION: "11.2.4",
                        ATTR_SKIPPED_VERSION: None,
                    },
                ),
                {"latest_version_firmware": LATEST_VERSION_FIRMWARE},
            )
        ],
    )
    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_ON
    assert state.attributes[ATTR_SKIPPED_VERSION] is None
    assert state.attributes[ATTR_LATEST_VERSION] == "11.2.4"

    client.async_send_command.return_value = {
        "result": {"status": 255, "success": True, "reInterview": False}
    }

    # Test successful install call without a version
    install_task = hass.async_create_task(
        hass.services.async_call(
            UPDATE_DOMAIN,
            SERVICE_INSTALL,
            {
                ATTR_ENTITY_ID: UPDATE_ENTITY,
            },
            blocking=True,
        )
    )

    # Sleep so that task starts
    await asyncio.sleep(0.1)

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    attrs = state.attributes
    assert attrs[ATTR_IN_PROGRESS] is True

    assert len(client.async_send_command.call_args_list) == 2
    assert client.async_send_command.call_args_list[1][0][0] == {
        "command": "controller.firmware_update_ota",
        "nodeId": climate_radio_thermostat_ct100_plus_different_endpoints.node_id,
        "updateInfo": {
            "version": "11.2.4",
            "changelog": "blah 2",
            "channel": "stable",
            "files": [
                {"target": 0, "url": "https://example2.com", "integrity": "sha2"}
            ],
            "downgrade": True,
            "normalizedVersion": "11.2.4",
            "device": {
                "manufacturerId": 1,
                "productType": 2,
                "productId": 3,
                "firmwareVersion": "0.4.4",
                "rfRegion": 1,
            },
        },
    }

    install_task.cancel()


async def test_update_entity_full_restore_data_no_update_available(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test entity with full restore data (no update available) restores state."""
    mock_restore_cache_with_extra_data(
        hass,
        [
            (
                State(
                    UPDATE_ENTITY,
                    STATE_OFF,
                    {
                        ATTR_INSTALLED_VERSION: "10.7",
                        ATTR_LATEST_VERSION: "10.7",
                        ATTR_SKIPPED_VERSION: None,
                    },
                ),
                {"latest_version_firmware": None},
            )
        ],
    )
    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF
    assert state.attributes[ATTR_SKIPPED_VERSION] is None
    assert state.attributes[ATTR_LATEST_VERSION] == "10.7"


async def test_update_entity_no_latest_version(
    hass: HomeAssistant,
    client,
    climate_radio_thermostat_ct100_plus_different_endpoints,
    hass_ws_client: WebSocketGenerator,
) -> None:
    """Test entity with no `latest_version` attr restores state."""
    mock_restore_cache_with_extra_data(
        hass,
        [
            (
                State(
                    UPDATE_ENTITY,
                    STATE_OFF,
                    {
                        ATTR_INSTALLED_VERSION: "10.7",
                        ATTR_LATEST_VERSION: None,
                        ATTR_SKIPPED_VERSION: None,
                    },
                ),
                {"latest_version_firmware": None},
            )
        ],
    )
    entry = MockConfigEntry(domain="zwave_js", data={"url": "ws://test.org"})
    entry.add_to_hass(hass)
    await hass.config_entries.async_setup(entry.entry_id)
    await hass.async_block_till_done()

    state = hass.states.get(UPDATE_ENTITY)
    assert state
    assert state.state == STATE_OFF
    assert state.attributes[ATTR_SKIPPED_VERSION] is None
    assert state.attributes[ATTR_LATEST_VERSION] == "10.7"


async def test_update_entity_unload_asleep_node(
    hass: HomeAssistant, client, wallmote_central_scene, integration
) -> None:
    """Test unloading config entry after attempting an update for an asleep node."""
    assert len(client.async_send_command.call_args_list) == 0

    async_fire_time_changed(hass, dt_util.utcnow() + timedelta(minutes=5, days=1))
    await hass.async_block_till_done()

    assert len(client.async_send_command.call_args_list) == 0
    assert len(wallmote_central_scene._listeners["wake up"]) == 2

    await hass.config_entries.async_unload(integration.entry_id)
    assert len(wallmote_central_scene._listeners["wake up"]) == 0
