"""Test homee covers."""

from collections.abc import AsyncGenerator
from unittest.mock import MagicMock, patch

import pytest
from syrupy.assertion import SnapshotAssertion
from websockets import frames
from websockets.exceptions import ConnectionClosed

from homeassistant.components.cover import (
    ATTR_POSITION,
    ATTR_TILT_POSITION,
    DOMAIN as COVER_DOMAIN,
    CoverEntityFeature,
    CoverState,
)
from homeassistant.components.homeassistant import (
    DOMAIN as HA_DOMAIN,
    SERVICE_UPDATE_ENTITY,
)
from homeassistant.components.homee.const import DOMAIN
from homeassistant.const import (
    ATTR_ENTITY_ID,
    SERVICE_CLOSE_COVER,
    SERVICE_CLOSE_COVER_TILT,
    SERVICE_OPEN_COVER,
    SERVICE_OPEN_COVER_TILT,
    SERVICE_SET_COVER_POSITION,
    SERVICE_SET_COVER_TILT_POSITION,
    SERVICE_STOP_COVER,
    SERVICE_STOP_COVER_TILT,
    STATE_UNAVAILABLE,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component

from . import build_mock_node, setup_integration

from tests.common import MockConfigEntry, snapshot_platform


@pytest.fixture(autouse=True)
async def platforms() -> AsyncGenerator[None]:
    """Return the platforms to be loaded for this test."""
    with patch("homeassistant.components.homee.PLATFORMS", [Platform.COVER]):
        yield


async def test_open_close_stop_cover(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test opening the cover."""
    mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
    mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]

    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_OPEN_COVER,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats"},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_CLOSE_COVER,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats"},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_STOP_COVER,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats"},
        blocking=True,
    )

    calls = mock_homee.set_value.call_args_list
    for index, call in enumerate(calls):
        assert call[0] == (mock_homee.nodes[0].id, 1, index)
        enum_value = call[0][2]
        assert f"{enum_value}" == str(index)


async def test_open_close_reverse_cover(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test opening the cover."""
    mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
    mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
    mock_homee.nodes[0].attributes[0].is_reversed = True

    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_OPEN_COVER,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats"},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_CLOSE_COVER,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats"},
        blocking=True,
    )

    calls = mock_homee.set_value.call_args_list
    assert calls[0][0] == (mock_homee.nodes[0].id, 1, 1)  # Open
    assert calls[1][0] == (mock_homee.nodes[0].id, 1, 0)  # Close

    for call in calls:
        enum_value = call[0][2]
        assert f"{enum_value}" in ("0", "1")


async def test_set_cover_position(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test setting the cover position."""
    mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
    mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]

    await setup_integration(hass, mock_config_entry)

    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_SET_COVER_POSITION,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats", ATTR_POSITION: 100},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_SET_COVER_POSITION,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats", ATTR_POSITION: 0},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_SET_COVER_POSITION,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats", ATTR_POSITION: 50},
        blocking=True,
    )

    calls = mock_homee.set_value.call_args_list
    positions = [0, 100, 50]
    for call in calls:
        assert call[0] == (3, 2, positions.pop(0))


async def test_close_open_slats(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test closing and opening slats."""
    mock_homee.nodes = [build_mock_node("cover_with_slats_position.json")]

    await setup_integration(hass, mock_config_entry)

    attributes = hass.states.get("cover.slats_position").attributes
    assert attributes.get("supported_features") == (
        CoverEntityFeature.OPEN_TILT
        | CoverEntityFeature.CLOSE_TILT
        | CoverEntityFeature.STOP_TILT
        | CoverEntityFeature.SET_TILT_POSITION
    )

    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_STOP_COVER_TILT,
        {ATTR_ENTITY_ID: "cover.slats_position"},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_CLOSE_COVER_TILT,
        {ATTR_ENTITY_ID: "cover.slats_position"},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_OPEN_COVER_TILT,
        {ATTR_ENTITY_ID: "cover.slats_position"},
        blocking=True,
    )

    calls = mock_homee.set_value.call_args_list
    for index, call in enumerate(calls):
        assert call[0] == (mock_homee.nodes[0].id, 2, index)
        enum_value = call[0][2]
        assert f"{enum_value}" == str(index)


async def test_close_open_reversed_slats(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test closing and opening slats."""
    mock_homee.nodes = [build_mock_node("cover_with_slats_position.json")]
    mock_homee.nodes[0].attributes[1].is_reversed = True

    await setup_integration(hass, mock_config_entry)

    attributes = hass.states.get("cover.slats_position").attributes
    assert attributes.get("supported_features") == (
        CoverEntityFeature.OPEN_TILT
        | CoverEntityFeature.CLOSE_TILT
        | CoverEntityFeature.STOP_TILT
        | CoverEntityFeature.SET_TILT_POSITION
    )

    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_CLOSE_COVER_TILT,
        {ATTR_ENTITY_ID: "cover.slats_position"},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_OPEN_COVER_TILT,
        {ATTR_ENTITY_ID: "cover.slats_position"},
        blocking=True,
    )

    calls = mock_homee.set_value.call_args_list
    assert calls[0][0] == (mock_homee.nodes[0].id, 2, 2)  # Close
    assert calls[1][0] == (mock_homee.nodes[0].id, 2, 1)  # Open

    for call in calls:
        enum_value = call[0][2]
        assert f"{enum_value}" in ("1", "2")


async def test_set_slat_position(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test setting slats position."""
    mock_homee.nodes = [build_mock_node("cover_with_slats_position.json")]

    await setup_integration(hass, mock_config_entry)

    # Slats have a range of -45 to 90 on this device.
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_SET_COVER_TILT_POSITION,
        {ATTR_ENTITY_ID: "cover.slats_position", ATTR_TILT_POSITION: 100},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_SET_COVER_TILT_POSITION,
        {ATTR_ENTITY_ID: "cover.slats_position", ATTR_TILT_POSITION: 0},
        blocking=True,
    )
    await hass.services.async_call(
        COVER_DOMAIN,
        SERVICE_SET_COVER_TILT_POSITION,
        {ATTR_ENTITY_ID: "cover.slats_position", ATTR_TILT_POSITION: 50},
        blocking=True,
    )

    calls = mock_homee.set_value.call_args_list
    positions = [-45, 90, 22.5]
    for call in calls:
        assert call[0] == (1, 1, positions.pop(0))


async def test_cover_positions(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test an open cover."""
    # Cover open, tilt open.
    # mock_homee.nodes = [cover]
    mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
    mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
    cover = mock_homee.nodes[0]

    await setup_integration(hass, mock_config_entry)

    assert (
        hass.states.get("cover.shutter_with_position_and_slats").state
        == CoverState.OPEN
    )

    attributes = hass.states.get("cover.shutter_with_position_and_slats").attributes
    assert attributes.get("supported_features") == (
        CoverEntityFeature.OPEN
        | CoverEntityFeature.CLOSE
        | CoverEntityFeature.SET_POSITION
        | CoverEntityFeature.STOP
        | CoverEntityFeature.SET_TILT_POSITION
    )
    assert attributes.get("current_position") == 100
    assert attributes.get("current_tilt_position") == 100

    cover.attributes[0].current_value = 1
    cover.attributes[1].current_value = 100
    cover.attributes[2].current_value = 90
    cover.add_on_changed_listener.call_args_list[0][0][0](cover)
    await hass.async_block_till_done()

    attributes = hass.states.get("cover.shutter_with_position_and_slats").attributes
    assert attributes.get("current_position") == 0
    assert attributes.get("current_tilt_position") == 0
    assert (
        hass.states.get("cover.shutter_with_position_and_slats").state
        == CoverState.CLOSED
    )

    cover.attributes[0].current_value = 3
    cover.attributes[1].current_value = 75
    cover.attributes[2].current_value = 56
    cover.add_on_changed_listener.call_args_list[0][0][0](cover)
    await hass.async_block_till_done()

    assert (
        hass.states.get("cover.shutter_with_position_and_slats").state
        == CoverState.OPENING
    )
    attributes = hass.states.get("cover.shutter_with_position_and_slats").attributes
    assert attributes.get("current_position") == 25
    assert attributes.get("current_tilt_position") == 25

    cover.attributes[0].current_value = 4
    cover.attributes[1].current_value = 25
    cover.attributes[2].current_value = -11
    cover.add_on_changed_listener.call_args_list[0][0][0](cover)
    await hass.async_block_till_done()

    assert (
        hass.states.get("cover.shutter_with_position_and_slats").state
        == CoverState.CLOSING
    )
    attributes = hass.states.get("cover.shutter_with_position_and_slats").attributes
    assert attributes.get("current_position") == 75
    assert attributes.get("current_tilt_position") == 74


async def test_reversed_cover(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test a cover with inverted UP_DOWN attribute without position."""
    mock_homee.nodes = [build_mock_node("cover_without_position.json")]
    cover = mock_homee.nodes[0]

    await setup_integration(hass, mock_config_entry)

    cover.attributes[0].is_reversed = True
    cover.add_on_changed_listener.call_args_list[0][0][0](cover)
    await hass.async_block_till_done()

    attributes = hass.states.get("cover.no_position").attributes
    assert attributes.get("supported_features") == (
        CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
    )
    assert hass.states.get("cover.no_position").state == CoverState.OPEN

    cover.attributes[0].current_value = 0
    cover.add_on_changed_listener.call_args_list[0][0][0](cover)
    await hass.async_block_till_done()

    assert hass.states.get("cover.no_position").state == CoverState.CLOSED


async def test_invalid_cover(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test a cover that has no Attribute for is_closed state."""
    mock_homee.nodes = [build_mock_node("cover_invalid.json")]
    await setup_integration(hass, mock_config_entry)

    assert "Cover Invalid Cover could not be added, " in caplog.text


async def test_send_error(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test failed set_value command."""
    mock_homee.nodes = [build_mock_node("cover_without_position.json")]

    await setup_integration(hass, mock_config_entry)

    mock_homee.set_value.side_effect = ConnectionClosed(
        rcvd=frames.Close(1002, "Protocol Error"), sent=None
    )
    with pytest.raises(HomeAssistantError) as exc_info:
        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: "cover.no_position"},
            blocking=True,
        )

    assert exc_info.value.translation_domain == DOMAIN
    assert exc_info.value.translation_key == "connection_closed"


async def test_node_entity_connection_listener(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test if loss of connection is sensed correctly."""
    mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
    mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
    await setup_integration(hass, mock_config_entry)

    states = hass.states.get("cover.shutter_with_position_and_slats")
    assert states.state != STATE_UNAVAILABLE

    await mock_homee.add_connection_listener.call_args_list[1][0][0](False)
    await hass.async_block_till_done()

    states = hass.states.get("cover.shutter_with_position_and_slats")
    assert states.state == STATE_UNAVAILABLE

    await mock_homee.add_connection_listener.call_args_list[1][0][0](True)
    await hass.async_block_till_done()

    states = hass.states.get("cover.shutter_with_position_and_slats")
    assert states.state != STATE_UNAVAILABLE


async def test_node_entity_update_action(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test the update_entity action for a HomeeEntity."""
    mock_homee.nodes = [build_mock_node("cover_with_position_slats.json")]
    mock_homee.get_node_by_id.return_value = mock_homee.nodes[0]
    await setup_integration(hass, mock_config_entry)
    await async_setup_component(hass, HA_DOMAIN, {})

    await hass.services.async_call(
        HA_DOMAIN,
        SERVICE_UPDATE_ENTITY,
        {ATTR_ENTITY_ID: "cover.shutter_with_position_and_slats"},
        blocking=True,
    )

    mock_homee.update_node.assert_called_once_with(3)


async def test_cover_snapshot(
    hass: HomeAssistant,
    mock_homee: MagicMock,
    mock_config_entry: MockConfigEntry,
    entity_registry: er.EntityRegistry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test snapshot of covers to assert cover_invalid is not created."""
    mock_homee.nodes = [
        build_mock_node("cover_with_slats_position.json"),
        build_mock_node("cover_without_position.json"),
        build_mock_node("cover_with_position_slats.json"),
        build_mock_node("cover_invalid.json"),
    ]
    mock_homee.get_node_by_id = lambda node_id: mock_homee.nodes[node_id - 1]
    await setup_integration(hass, mock_config_entry)

    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
