"""Tests for the humidifier platform."""

from contextlib import nullcontext
import logging
from unittest.mock import patch

import pytest

from homeassistant.components.humidifier import (
    ATTR_HUMIDITY,
    ATTR_MODE,
    DOMAIN as HUMIDIFIER_DOMAIN,
    MODE_AUTO,
    MODE_SLEEP,
    SERVICE_SET_HUMIDITY,
    SERVICE_SET_MODE,
)
from homeassistant.components.vesync.const import (
    VS_HUMIDIFIER_MODE_AUTO,
    VS_HUMIDIFIER_MODE_MANUAL,
    VS_HUMIDIFIER_MODE_SLEEP,
)
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import (
    ATTR_ENTITY_ID,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
    STATE_UNAVAILABLE,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er

from .common import (
    ENTITY_HUMIDIFIER,
    ENTITY_HUMIDIFIER_HUMIDITY,
    ENTITY_HUMIDIFIER_MIST_LEVEL,
)

from tests.common import MockConfigEntry

NoException = nullcontext()


async def test_humidifier_state(
    hass: HomeAssistant, humidifier_config_entry: MockConfigEntry
) -> None:
    """Test the resulting setup state is as expected for the platform."""

    expected_entities = [
        ENTITY_HUMIDIFIER,
        ENTITY_HUMIDIFIER_HUMIDITY,
        ENTITY_HUMIDIFIER_MIST_LEVEL,
    ]

    assert humidifier_config_entry.state is ConfigEntryState.LOADED

    for entity_id in expected_entities:
        assert hass.states.get(entity_id).state != STATE_UNAVAILABLE

    state = hass.states.get(ENTITY_HUMIDIFIER)

    # ATTR_HUMIDITY represents the target_humidity which comes from configuration.auto_target_humidity node
    assert state.attributes.get(ATTR_HUMIDITY) == 40


async def test_set_target_humidity_invalid(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
) -> None:
    """Test handling of invalid value in set_humidify method."""

    # Setting value out of range results in ServiceValidationError and
    # VeSyncHumid200300S.set_humidity does not get called.
    with (
        patch("pyvesync.vesyncfan.VeSyncHumid200300S.set_humidity") as method_mock,
        pytest.raises(ServiceValidationError),
    ):
        await hass.services.async_call(
            HUMIDIFIER_DOMAIN,
            SERVICE_SET_HUMIDITY,
            {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER, ATTR_HUMIDITY: 20},
            blocking=True,
        )
    await hass.async_block_till_done()
    method_mock.assert_not_called()


@pytest.mark.parametrize(
    ("api_response", "expectation"),
    [(True, NoException), (False, pytest.raises(HomeAssistantError))],
)
async def test_set_target_humidity(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
    api_response: bool,
    expectation,
) -> None:
    """Test handling of return value from VeSyncHumid200300S.set_humidity."""

    # If VeSyncHumid200300S.set_humidity fails (returns False), then HomeAssistantError is raised
    with (
        expectation,
        patch(
            "pyvesync.vesyncfan.VeSyncHumid200300S.set_humidity",
            return_value=api_response,
        ) as method_mock,
    ):
        await hass.services.async_call(
            HUMIDIFIER_DOMAIN,
            SERVICE_SET_HUMIDITY,
            {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER, ATTR_HUMIDITY: 54},
            blocking=True,
        )
        await hass.async_block_till_done()
        method_mock.assert_called_once()


@pytest.mark.parametrize(
    ("api_response", "expectation"),
    [(False, pytest.raises(HomeAssistantError)), (True, NoException)],
)
async def test_turn_on(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
    api_response: bool,
    expectation,
) -> None:
    """Test turn_on method."""

    # turn_on returns False indicating failure in which case humidifier.turn_on
    # raises HomeAssistantError.
    with (
        expectation,
        patch(
            "pyvesync.vesyncfan.VeSyncHumid200300S.turn_on", return_value=api_response
        ) as method_mock,
    ):
        with patch(
            "homeassistant.components.vesync.humidifier.VeSyncHumidifierHA.schedule_update_ha_state"
        ) as update_mock:
            await hass.services.async_call(
                HUMIDIFIER_DOMAIN,
                SERVICE_TURN_ON,
                {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER},
                blocking=True,
            )

        await hass.async_block_till_done()
        method_mock.assert_called_once()
        update_mock.assert_called_once()


@pytest.mark.parametrize(
    ("api_response", "expectation"),
    [(False, pytest.raises(HomeAssistantError)), (True, NoException)],
)
async def test_turn_off(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
    api_response: bool,
    expectation,
) -> None:
    """Test turn_off method."""

    # turn_off returns False indicating failure in which case humidifier.turn_off
    # raises HomeAssistantError.
    with (
        expectation,
        patch(
            "pyvesync.vesyncfan.VeSyncHumid200300S.turn_off", return_value=api_response
        ) as method_mock,
    ):
        with patch(
            "homeassistant.components.vesync.humidifier.VeSyncHumidifierHA.schedule_update_ha_state"
        ) as update_mock:
            await hass.services.async_call(
                HUMIDIFIER_DOMAIN,
                SERVICE_TURN_OFF,
                {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER},
                blocking=True,
            )

        await hass.async_block_till_done()
        method_mock.assert_called_once()
        update_mock.assert_called_once()


async def test_set_mode_invalid(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
) -> None:
    """Test handling of invalid value in set_mode method."""

    with patch(
        "pyvesync.vesyncfan.VeSyncHumid200300S.set_humidity_mode"
    ) as method_mock:
        with pytest.raises(HomeAssistantError):
            await hass.services.async_call(
                HUMIDIFIER_DOMAIN,
                SERVICE_SET_MODE,
                {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER, ATTR_MODE: "something_invalid"},
                blocking=True,
            )
        await hass.async_block_till_done()
        method_mock.assert_not_called()


@pytest.mark.parametrize(
    ("api_response", "expectation"),
    [(True, NoException), (False, pytest.raises(HomeAssistantError))],
)
async def test_set_mode(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
    api_response: bool,
    expectation,
) -> None:
    """Test handling of value in set_mode method."""

    # If VeSyncHumid200300S.set_humidity_mode fails (returns False), then HomeAssistantError is raised
    with (
        expectation,
        patch(
            "pyvesync.vesyncfan.VeSyncHumid200300S.set_humidity_mode",
            return_value=api_response,
        ) as method_mock,
    ):
        await hass.services.async_call(
            HUMIDIFIER_DOMAIN,
            SERVICE_SET_MODE,
            {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER, ATTR_MODE: MODE_AUTO},
            blocking=True,
        )
    await hass.async_block_till_done()
    method_mock.assert_called_once()


async def test_base_unique_id(
    hass: HomeAssistant,
    humidifier_config_entry: MockConfigEntry,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test that unique_id is based on subDeviceNo."""
    # vesync-device.json defines subDeviceNo for 200s-humidifier as 4321.
    entity = entity_registry.async_get(ENTITY_HUMIDIFIER)
    assert entity.unique_id.endswith("4321")


async def test_invalid_mist_modes(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    humidifier,
    manager,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test unsupported mist mode."""

    humidifier.mist_modes = ["invalid_mode"]

    with patch(
        "homeassistant.components.vesync.async_generate_device_list",
        return_value=[humidifier],
    ):
        caplog.clear()
        caplog.set_level(logging.WARNING)

        await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()
        assert "Unknown mode 'invalid_mode'" in caplog.text


async def test_valid_mist_modes(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    humidifier,
    manager,
    caplog: pytest.LogCaptureFixture,
) -> None:
    """Test supported mist mode."""

    humidifier.mist_modes = ["auto", "manual"]

    with patch(
        "homeassistant.components.vesync.async_generate_device_list",
        return_value=[humidifier],
    ):
        caplog.clear()
        caplog.set_level(logging.WARNING)

        await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()
        assert "Unknown mode 'auto'" not in caplog.text
        assert "Unknown mode 'manual'" not in caplog.text


async def test_set_mode_sleep_turns_display_off(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    humidifier,
    manager,
) -> None:
    """Test update of display for sleep mode."""

    # First define valid mist modes
    humidifier.mist_modes = [
        VS_HUMIDIFIER_MODE_AUTO,
        VS_HUMIDIFIER_MODE_MANUAL,
        VS_HUMIDIFIER_MODE_SLEEP,
    ]

    with patch(
        "homeassistant.components.vesync.async_generate_device_list",
        return_value=[humidifier],
    ):
        await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

    with (
        patch.object(humidifier, "set_humidity_mode", return_value=True),
        patch.object(humidifier, "set_display") as display_mock,
    ):
        await hass.services.async_call(
            HUMIDIFIER_DOMAIN,
            SERVICE_SET_MODE,
            {ATTR_ENTITY_ID: ENTITY_HUMIDIFIER, ATTR_MODE: MODE_SLEEP},
            blocking=True,
        )
        display_mock.assert_called_once_with(False)