"""Test for the LCN cover platform."""

from unittest.mock import AsyncMock, patch

from freezegun.api import FrozenDateTimeFactory
from pypck.inputs import (
    ModStatusMotorPositionBS4,
    ModStatusMotorPositionModule,
    ModStatusOutput,
    ModStatusRelays,
)
from pypck.lcn_addr import LcnAddr
from pypck.lcn_defs import MotorPositioningMode, MotorReverseTime, MotorStateModifier
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.cover import (
    ATTR_CURRENT_POSITION,
    ATTR_POSITION,
    DOMAIN as COVER_DOMAIN,
    CoverState,
)
from homeassistant.components.lcn.cover import SCAN_INTERVAL
from homeassistant.components.lcn.helpers import get_device_connection
from homeassistant.const import (
    ATTR_ENTITY_ID,
    SERVICE_CLOSE_COVER,
    SERVICE_OPEN_COVER,
    SERVICE_SET_COVER_POSITION,
    SERVICE_STOP_COVER,
    STATE_UNAVAILABLE,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er

from .conftest import MockConfigEntry, MockDeviceConnection, init_integration

from tests.common import async_fire_time_changed, snapshot_platform

COVER_OUTPUTS = "cover.testmodule_cover_outputs"
COVER_RELAYS = "cover.testmodule_cover_relays"
COVER_RELAYS_BS4 = "cover.testmodule_cover_relays_bs4"
COVER_RELAYS_MODULE = "cover.testmodule_cover_relays_module"


async def test_setup_lcn_cover(
    hass: HomeAssistant,
    entity_registry: er.EntityRegistry,
    entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test the setup of cover."""
    with patch("homeassistant.components.lcn.PLATFORMS", [Platform.COVER]):
        await init_integration(hass, entry)

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


async def test_outputs_open(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the outputs cover opens."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_outputs"
    ) as control_motor_outputs:
        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state == CoverState.CLOSED

        # command failed
        control_motor_outputs.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        control_motor_outputs.assert_awaited_with(
            MotorStateModifier.UP, MotorReverseTime.RT1200
        )

        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state != CoverState.OPENING

        # command success
        control_motor_outputs.reset_mock(return_value=True)
        control_motor_outputs.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        control_motor_outputs.assert_awaited_with(
            MotorStateModifier.UP, MotorReverseTime.RT1200
        )

        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state == CoverState.OPENING


async def test_outputs_close(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the outputs cover closes."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_outputs"
    ) as control_motor_outputs:
        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        # command failed
        control_motor_outputs.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_CLOSE_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        control_motor_outputs.assert_awaited_with(
            MotorStateModifier.DOWN, MotorReverseTime.RT1200
        )

        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state != CoverState.CLOSING

        # command success
        control_motor_outputs.reset_mock(return_value=True)
        control_motor_outputs.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_CLOSE_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        control_motor_outputs.assert_awaited_with(
            MotorStateModifier.DOWN, MotorReverseTime.RT1200
        )

        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state == CoverState.CLOSING


async def test_outputs_stop(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the outputs cover stops."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_outputs"
    ) as control_motor_outputs:
        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_CLOSE_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        # command failed
        control_motor_outputs.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_STOP_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        control_motor_outputs.assert_awaited_with(MotorStateModifier.STOP)

        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state == CoverState.CLOSING

        # command success
        control_motor_outputs.reset_mock(return_value=True)
        control_motor_outputs.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_STOP_COVER,
            {ATTR_ENTITY_ID: COVER_OUTPUTS},
            blocking=True,
        )

        control_motor_outputs.assert_awaited_with(MotorStateModifier.STOP)

        state = hass.states.get(COVER_OUTPUTS)
        assert state is not None
        assert state.state not in (CoverState.CLOSING, CoverState.OPENING)


async def test_relays_open(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the relays cover opens."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_relays"
    ) as control_motor_relays:
        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state == CoverState.CLOSED

        # command failed
        control_motor_relays.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        control_motor_relays.assert_awaited_with(
            0, MotorStateModifier.UP, MotorPositioningMode.NONE
        )

        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state != CoverState.OPENING

        # command success
        control_motor_relays.reset_mock(return_value=True)
        control_motor_relays.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        control_motor_relays.assert_awaited_with(
            0, MotorStateModifier.UP, MotorPositioningMode.NONE
        )

        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state == CoverState.OPENING


async def test_relays_close(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the relays cover closes."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_relays"
    ) as control_motor_relays:
        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_OPEN_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        # command failed
        control_motor_relays.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_CLOSE_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        control_motor_relays.assert_awaited_with(
            0, MotorStateModifier.DOWN, MotorPositioningMode.NONE
        )

        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state != CoverState.CLOSING

        # command success
        control_motor_relays.reset_mock(return_value=True)
        control_motor_relays.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_CLOSE_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        control_motor_relays.assert_awaited_with(
            0, MotorStateModifier.DOWN, MotorPositioningMode.NONE
        )

        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state == CoverState.CLOSING


async def test_relays_stop(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the relays cover stops."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_relays"
    ) as control_motor_relays:
        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_CLOSE_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        # command failed
        control_motor_relays.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_STOP_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        control_motor_relays.assert_awaited_with(
            0, MotorStateModifier.STOP, MotorPositioningMode.NONE
        )

        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state == CoverState.CLOSING

        # command success
        control_motor_relays.reset_mock(return_value=True)
        control_motor_relays.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_STOP_COVER,
            {ATTR_ENTITY_ID: COVER_RELAYS},
            blocking=True,
        )

        control_motor_relays.assert_awaited_with(
            0, MotorStateModifier.STOP, MotorPositioningMode.NONE
        )

        state = hass.states.get(COVER_RELAYS)
        assert state is not None
        assert state.state not in (CoverState.CLOSING, CoverState.OPENING)


@pytest.mark.parametrize(
    ("entity_id", "motor", "positioning_mode"),
    [
        (COVER_RELAYS_BS4, 1, MotorPositioningMode.BS4),
        (COVER_RELAYS_MODULE, 2, MotorPositioningMode.MODULE),
    ],
)
async def test_relays_set_position(
    hass: HomeAssistant,
    entry: MockConfigEntry,
    entity_id: str,
    motor: int,
    positioning_mode: MotorPositioningMode,
) -> None:
    """Test the relays cover moves to position."""
    await init_integration(hass, entry)

    with patch.object(
        MockDeviceConnection, "control_motor_relays_position"
    ) as control_motor_relays_position:
        state = hass.states.get(entity_id)
        assert state is not None
        assert state.state == CoverState.CLOSED

        # command failed
        control_motor_relays_position.return_value = False

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_SET_COVER_POSITION,
            {ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50},
            blocking=True,
        )

        control_motor_relays_position.assert_awaited_with(
            motor, 50, mode=positioning_mode
        )

        state = hass.states.get(entity_id)
        assert state.state == CoverState.CLOSED

        # command success
        control_motor_relays_position.reset_mock(return_value=True)
        control_motor_relays_position.return_value = True

        await hass.services.async_call(
            COVER_DOMAIN,
            SERVICE_SET_COVER_POSITION,
            {ATTR_ENTITY_ID: entity_id, ATTR_POSITION: 50},
            blocking=True,
        )

        control_motor_relays_position.assert_awaited_with(
            motor, 50, mode=positioning_mode
        )

        state = hass.states.get(entity_id)
        assert state.state == CoverState.OPEN


async def test_pushed_outputs_status_change(
    hass: HomeAssistant, entry: MockConfigEntry
) -> None:
    """Test the outputs cover changes its state on status received."""
    await init_integration(hass, entry)

    device_connection = get_device_connection(hass, (0, 7, False), entry)
    address = LcnAddr(0, 7, False)

    state = hass.states.get(COVER_OUTPUTS)
    state.state = CoverState.CLOSED

    # push status "open"
    inp = ModStatusOutput(address, 0, 100)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_OUTPUTS)
    assert state is not None
    assert state.state == CoverState.OPENING

    # push status "stop"
    inp = ModStatusOutput(address, 0, 0)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_OUTPUTS)
    assert state is not None
    assert state.state not in (CoverState.OPENING, CoverState.CLOSING)

    # push status "close"
    inp = ModStatusOutput(address, 1, 100)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_OUTPUTS)
    assert state is not None
    assert state.state == CoverState.CLOSING


async def test_pushed_relays_status_change(
    hass: HomeAssistant, entry: MockConfigEntry
) -> None:
    """Test the relays cover changes its state on status received."""
    await init_integration(hass, entry)

    device_connection = get_device_connection(hass, (0, 7, False), entry)
    address = LcnAddr(0, 7, False)
    states = [False] * 8

    for entity_id in (COVER_RELAYS, COVER_RELAYS_BS4, COVER_RELAYS_MODULE):
        state = hass.states.get(entity_id)
        state.state = CoverState.CLOSED

    # push status "open"
    states[0:2] = [True, False]
    inp = ModStatusRelays(address, states)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_RELAYS)
    assert state is not None
    assert state.state == CoverState.OPENING

    # push status "stop"
    states[0] = False
    inp = ModStatusRelays(address, states)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_RELAYS)
    assert state is not None
    assert state.state not in (CoverState.OPENING, CoverState.CLOSING)

    # push status "close"
    states[0:2] = [True, True]
    inp = ModStatusRelays(address, states)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_RELAYS)
    assert state is not None
    assert state.state == CoverState.CLOSING

    # push status "set position" via BS4
    inp = ModStatusMotorPositionBS4(address, 1, 50)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_RELAYS_BS4)
    assert state is not None
    assert state.state == CoverState.OPEN
    assert state.attributes[ATTR_CURRENT_POSITION] == 50

    # push status "set position" via MODULE
    inp = ModStatusMotorPositionModule(address, 2, 75)
    await device_connection.async_process_input(inp)
    await hass.async_block_till_done()

    state = hass.states.get(COVER_RELAYS_MODULE)
    assert state is not None
    assert state.state == CoverState.OPEN
    assert state.attributes[ATTR_CURRENT_POSITION] == 75


@pytest.mark.parametrize(
    ("entity_id", "request_methods", "return_values"),
    [
        (
            COVER_OUTPUTS,
            ("request_status_output",),
            (ModStatusOutput(LcnAddr(0, 7, False), 0, 100),),
        ),
        (
            COVER_RELAYS,
            ("request_status_relays",),
            (ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),),
        ),
        (
            COVER_RELAYS_BS4,
            ("request_status_relays", "request_status_motor_position"),
            (
                ModStatusRelays(LcnAddr(0, 7, False), [False] * 8),
                ModStatusMotorPositionBS4(LcnAddr(0, 7, False), 1, 50),
            ),
        ),
    ],
)
async def test_availability(
    hass: HomeAssistant,
    freezer: FrozenDateTimeFactory,
    entry: MockConfigEntry,
    entity_id: str,
    request_methods: list[str],
    return_values: list[ModStatusOutput | ModStatusRelays],
) -> None:
    """Test the availability of cover entity."""
    await init_integration(hass, entry)

    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state != STATE_UNAVAILABLE

    # no response from device -> unavailable
    with patch.multiple(
        MockDeviceConnection,
        **{
            request_method: AsyncMock(return_value=None)
            for request_method in request_methods
        },
    ):
        freezer.tick(SCAN_INTERVAL)
        async_fire_time_changed(hass)
        await hass.async_block_till_done(wait_background_tasks=True)

    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state == STATE_UNAVAILABLE

    with patch.multiple(
        MockDeviceConnection,
        **{
            request_method: AsyncMock(return_value=return_value)
            for request_method, return_value in zip(
                request_methods, return_values, strict=True
            )
        },
    ):
        freezer.tick(SCAN_INTERVAL)
        async_fire_time_changed(hass)
        await hass.async_block_till_done(wait_background_tasks=True)

    state = hass.states.get(entity_id)
    assert state is not None
    assert state.state != STATE_UNAVAILABLE


async def test_unload_config_entry(hass: HomeAssistant, entry: MockConfigEntry) -> None:
    """Test the cover is removed when the config entry is unloaded."""
    await init_integration(hass, entry)

    await hass.config_entries.async_unload(entry.entry_id)
    assert hass.states.get(COVER_OUTPUTS).state == STATE_UNAVAILABLE
    assert hass.states.get(COVER_RELAYS).state == STATE_UNAVAILABLE
