"""Tests for Vallox fan platform."""
from unittest.mock import call

import pytest
from vallox_websocket_api import PROFILE, ValloxApiException

from homeassistant.components.fan import (
    ATTR_PERCENTAGE,
    ATTR_PRESET_MODE,
    DOMAIN as FAN_DOMAIN,
    SERVICE_SET_PERCENTAGE,
    SERVICE_SET_PRESET_MODE,
)
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError

from .conftest import patch_metrics, patch_metrics_set, patch_profile, patch_profile_set

from tests.common import MockConfigEntry


@pytest.mark.parametrize(
    ("metrics", "expected_state"),
    [({"A_CYC_MODE": 0}, "on"), ({"A_CYC_MODE": 5}, "off")],
)
async def test_fan_state(
    metrics: dict[str, int],
    expected_state: str,
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test fan on/off state."""

    # Act
    with patch_metrics(metrics=metrics):
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()

    # Assert
    sensor = hass.states.get("fan.vallox")
    assert sensor
    assert sensor.state == expected_state


@pytest.mark.parametrize(
    ("profile", "expected_preset"),
    [
        (PROFILE.HOME, "Home"),
        (PROFILE.AWAY, "Away"),
        (PROFILE.BOOST, "Boost"),
        (PROFILE.FIREPLACE, "Fireplace"),
    ],
)
async def test_fan_profile(
    profile: PROFILE,
    expected_preset: str,
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test fan profile."""

    # Act
    with patch_profile(profile):
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()

    # Assert
    sensor = hass.states.get("fan.vallox")
    assert sensor
    assert sensor.attributes["preset_mode"] == expected_preset


@pytest.mark.parametrize(
    ("service", "initial_metrics", "expected_called_with"),
    [
        (SERVICE_TURN_ON, {"A_CYC_MODE": 5}, {"A_CYC_MODE": 0}),
        (SERVICE_TURN_OFF, {"A_CYC_MODE": 0}, {"A_CYC_MODE": 5}),
    ],
)
async def test_turn_on_off(
    service: str,
    initial_metrics: dict[str, int],
    expected_called_with: dict[str, int],
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test turn on/off."""
    with patch_metrics(metrics=initial_metrics), patch_metrics_set() as metrics_set:
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()
        await hass.services.async_call(
            FAN_DOMAIN,
            service,
            service_data={ATTR_ENTITY_ID: "fan.vallox"},
            blocking=True,
        )
        metrics_set.assert_called_once_with(expected_called_with)


@pytest.mark.parametrize(
    ("initial_metrics", "expected_call_args_list"),
    [
        (
            {"A_CYC_MODE": 5},
            [
                call({"A_CYC_MODE": 0}),
                call({"A_CYC_AWAY_SPEED_SETTING": 15}),
            ],
        ),
        (
            {"A_CYC_MODE": 0},
            [
                call({"A_CYC_AWAY_SPEED_SETTING": 15}),
            ],
        ),
    ],
)
async def test_turn_on_with_parameters(
    initial_metrics: dict[str, int],
    expected_call_args_list: list[tuple],
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test turn on/off."""
    with patch_metrics(
        metrics=initial_metrics
    ), patch_metrics_set() as metrics_set, patch_profile_set() as profile_set:
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()
        await hass.services.async_call(
            FAN_DOMAIN,
            SERVICE_TURN_ON,
            service_data={
                ATTR_ENTITY_ID: "fan.vallox",
                ATTR_PERCENTAGE: "15",
                ATTR_PRESET_MODE: "Away",
            },
            blocking=True,
        )
        assert metrics_set.call_args_list == expected_call_args_list
        profile_set.assert_called_once_with(PROFILE.AWAY)


@pytest.mark.parametrize(
    ("preset", "initial_profile", "expected_call_args_list"),
    [
        ("Home", PROFILE.AWAY, [call(PROFILE.HOME)]),
        ("Away", PROFILE.HOME, [call(PROFILE.AWAY)]),
        ("Boost", PROFILE.HOME, [call(PROFILE.BOOST)]),
        ("Fireplace", PROFILE.HOME, [call(PROFILE.FIREPLACE)]),
        ("Home", PROFILE.HOME, []),
    ],
)
async def test_set_preset_mode(
    preset: str,
    initial_profile: PROFILE,
    expected_call_args_list: list[tuple],
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test set preset mode."""
    with patch_profile(initial_profile), patch_profile_set() as profile_set:
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()
        await hass.services.async_call(
            FAN_DOMAIN,
            SERVICE_SET_PRESET_MODE,
            service_data={ATTR_ENTITY_ID: "fan.vallox", ATTR_PRESET_MODE: preset},
            blocking=True,
        )
        assert profile_set.call_args_list == expected_call_args_list


async def test_set_invalid_preset_mode(
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test set preset mode."""
    await hass.config_entries.async_setup(mock_entry.entry_id)
    await hass.async_block_till_done()
    with pytest.raises(ValueError):
        await hass.services.async_call(
            FAN_DOMAIN,
            SERVICE_SET_PRESET_MODE,
            service_data={
                ATTR_ENTITY_ID: "fan.vallox",
                ATTR_PRESET_MODE: "Invalid",
            },
            blocking=True,
        )


async def test_set_preset_mode_exception(
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test set preset mode."""
    with patch_profile_set() as profile_set:
        profile_set.side_effect = ValloxApiException("Fake exception")
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()
        with pytest.raises(HomeAssistantError):
            await hass.services.async_call(
                FAN_DOMAIN,
                SERVICE_SET_PRESET_MODE,
                service_data={ATTR_ENTITY_ID: "fan.vallox", ATTR_PRESET_MODE: "Away"},
                blocking=True,
            )


@pytest.mark.parametrize(
    ("profile", "percentage", "expected_call_args_list"),
    [
        (PROFILE.HOME, 40, [call({"A_CYC_HOME_SPEED_SETTING": 40})]),
        (PROFILE.AWAY, 30, [call({"A_CYC_AWAY_SPEED_SETTING": 30})]),
        (PROFILE.BOOST, 60, [call({"A_CYC_BOOST_SPEED_SETTING": 60})]),
        (PROFILE.HOME, 0, [call({"A_CYC_MODE": 5})]),
    ],
)
async def test_set_fan_speed(
    profile: PROFILE,
    percentage: int,
    expected_call_args_list: list[tuple],
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test set fan speed percentage."""
    with patch_profile(profile), patch_metrics_set() as metrics_set, patch_metrics(
        {"A_CYC_MODE": 0}
    ):
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()
        await hass.services.async_call(
            FAN_DOMAIN,
            SERVICE_SET_PERCENTAGE,
            service_data={ATTR_ENTITY_ID: "fan.vallox", ATTR_PERCENTAGE: percentage},
            blocking=True,
        )
        assert metrics_set.call_args_list == expected_call_args_list


async def test_set_fan_speed_exception(
    mock_entry: MockConfigEntry,
    hass: HomeAssistant,
) -> None:
    """Test set fan speed percentage."""
    with patch_metrics_set() as metrics_set, patch_metrics(
        {"A_CYC_MODE": 0, "A_CYC_HOME_SPEED_SETTING": 30}
    ):
        metrics_set.side_effect = ValloxApiException("Fake failure")
        await hass.config_entries.async_setup(mock_entry.entry_id)
        await hass.async_block_till_done()
        with pytest.raises(HomeAssistantError):
            await hass.services.async_call(
                FAN_DOMAIN,
                SERVICE_SET_PERCENTAGE,
                service_data={ATTR_ENTITY_ID: "fan.vallox", ATTR_PERCENTAGE: 5},
                blocking=True,
            )