"""Test the Whirlpool Sixth Sense climate domain."""

from unittest.mock import MagicMock

import pytest
from syrupy.assertion import SnapshotAssertion
import whirlpool

from homeassistant.components.climate import (
    ATTR_CURRENT_HUMIDITY,
    ATTR_CURRENT_TEMPERATURE,
    ATTR_FAN_MODE,
    ATTR_HVAC_MODE,
    ATTR_SWING_MODE,
    DOMAIN as CLIMATE_DOMAIN,
    FAN_AUTO,
    FAN_HIGH,
    FAN_LOW,
    FAN_MEDIUM,
    FAN_MIDDLE,
    FAN_OFF,
    SERVICE_SET_FAN_MODE,
    SERVICE_SET_HVAC_MODE,
    SERVICE_SET_SWING_MODE,
    SERVICE_SET_TEMPERATURE,
    SWING_HORIZONTAL,
    SWING_OFF,
    HVACMode,
)
from homeassistant.const import (
    ATTR_ENTITY_ID,
    ATTR_TEMPERATURE,
    SERVICE_TURN_OFF,
    SERVICE_TURN_ON,
    STATE_UNAVAILABLE,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import entity_registry as er

from . import init_integration, snapshot_whirlpool_entities, trigger_attr_callback


@pytest.fixture(
    params=[
        ("climate.aircon_said1", "mock_aircon1_api"),
        ("climate.aircon_said2", "mock_aircon2_api"),
    ]
)
def multiple_climate_entities(request: pytest.FixtureRequest) -> tuple[str, str]:
    """Fixture for multiple climate entities."""
    entity_id, mock_fixture = request.param
    return entity_id, mock_fixture


async def update_ac_state(
    hass: HomeAssistant,
    entity_id: str,
    mock_aircon_api_instance: MagicMock,
):
    """Simulate an update trigger from the API."""
    await trigger_attr_callback(hass, mock_aircon_api_instance)
    return hass.states.get(entity_id)


@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_all_entities(
    hass: HomeAssistant,
    snapshot: SnapshotAssertion,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test all entities."""
    await init_integration(hass)
    snapshot_whirlpool_entities(hass, entity_registry, snapshot, Platform.CLIMATE)


async def test_dynamic_attributes(
    hass: HomeAssistant,
    multiple_climate_entities: tuple[str, str],
    request: pytest.FixtureRequest,
) -> None:
    """Test dynamic attributes."""
    entity_id, mock_fixture = multiple_climate_entities
    mock_instance = request.getfixturevalue(mock_fixture)
    await init_integration(hass)

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

    mock_instance.get_power_on.return_value = False
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == HVACMode.OFF

    mock_instance.get_online.return_value = False
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == STATE_UNAVAILABLE

    mock_instance.get_power_on.return_value = True
    mock_instance.get_online.return_value = True
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == HVACMode.COOL

    mock_instance.get_mode.return_value = whirlpool.aircon.Mode.Heat
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == HVACMode.HEAT

    mock_instance.get_mode.return_value = whirlpool.aircon.Mode.Fan
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == HVACMode.FAN_ONLY

    mock_instance.get_fanspeed.return_value = whirlpool.aircon.FanSpeed.Auto
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.attributes[ATTR_FAN_MODE] == HVACMode.AUTO

    mock_instance.get_fanspeed.return_value = whirlpool.aircon.FanSpeed.Low
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.attributes[ATTR_FAN_MODE] == FAN_LOW

    mock_instance.get_fanspeed.return_value = whirlpool.aircon.FanSpeed.Medium
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.attributes[ATTR_FAN_MODE] == FAN_MEDIUM

    mock_instance.get_fanspeed.return_value = whirlpool.aircon.FanSpeed.High
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.attributes[ATTR_FAN_MODE] == FAN_HIGH

    mock_instance.get_fanspeed.return_value = whirlpool.aircon.FanSpeed.Off
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.attributes[ATTR_FAN_MODE] == FAN_OFF

    mock_instance.get_current_temp.return_value = 15
    mock_instance.get_temp.return_value = 20
    mock_instance.get_current_humidity.return_value = 80
    mock_instance.get_h_louver_swing.return_value = True
    attributes = (await update_ac_state(hass, entity_id, mock_instance)).attributes
    assert attributes[ATTR_CURRENT_TEMPERATURE] == 15
    assert attributes[ATTR_TEMPERATURE] == 20
    assert attributes[ATTR_CURRENT_HUMIDITY] == 80
    assert attributes[ATTR_SWING_MODE] == SWING_HORIZONTAL

    mock_instance.get_current_temp.return_value = 16
    mock_instance.get_temp.return_value = 21
    mock_instance.get_current_humidity.return_value = 70
    mock_instance.get_h_louver_swing.return_value = False
    attributes = (await update_ac_state(hass, entity_id, mock_instance)).attributes
    assert attributes[ATTR_CURRENT_TEMPERATURE] == 16
    assert attributes[ATTR_TEMPERATURE] == 21
    assert attributes[ATTR_CURRENT_HUMIDITY] == 70
    assert attributes[ATTR_SWING_MODE] == SWING_OFF


@pytest.mark.parametrize(
    ("service", "service_data", "expected_call", "expected_args"),
    [
        (SERVICE_TURN_OFF, {}, "set_power_on", [False]),
        (SERVICE_TURN_ON, {}, "set_power_on", [True]),
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.COOL},
            "set_mode",
            [whirlpool.aircon.Mode.Cool],
        ),
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.HEAT},
            "set_mode",
            [whirlpool.aircon.Mode.Heat],
        ),
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.FAN_ONLY},
            "set_mode",
            [whirlpool.aircon.Mode.Fan],
        ),
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.OFF},
            "set_power_on",
            [False],
        ),
        (SERVICE_SET_TEMPERATURE, {ATTR_TEMPERATURE: 20}, "set_temp", [20]),
        (
            SERVICE_SET_FAN_MODE,
            {ATTR_FAN_MODE: FAN_AUTO},
            "set_fanspeed",
            [whirlpool.aircon.FanSpeed.Auto],
        ),
        (
            SERVICE_SET_FAN_MODE,
            {ATTR_FAN_MODE: FAN_LOW},
            "set_fanspeed",
            [whirlpool.aircon.FanSpeed.Low],
        ),
        (
            SERVICE_SET_FAN_MODE,
            {ATTR_FAN_MODE: FAN_MEDIUM},
            "set_fanspeed",
            [whirlpool.aircon.FanSpeed.Medium],
        ),
        (
            SERVICE_SET_FAN_MODE,
            {ATTR_FAN_MODE: FAN_HIGH},
            "set_fanspeed",
            [whirlpool.aircon.FanSpeed.High],
        ),
        (
            SERVICE_SET_SWING_MODE,
            {ATTR_SWING_MODE: SWING_HORIZONTAL},
            "set_h_louver_swing",
            [True],
        ),
        (
            SERVICE_SET_SWING_MODE,
            {ATTR_SWING_MODE: SWING_OFF},
            "set_h_louver_swing",
            [False],
        ),
    ],
)
async def test_service_calls(
    hass: HomeAssistant,
    service: str,
    service_data: dict,
    expected_call: str,
    expected_args: list,
    multiple_climate_entities: tuple[str, str],
    request: pytest.FixtureRequest,
) -> None:
    """Test controlling the entity through service calls."""
    await init_integration(hass)
    entity_id, mock_fixture = multiple_climate_entities
    mock_instance = request.getfixturevalue(mock_fixture)

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        service,
        {ATTR_ENTITY_ID: entity_id, **service_data},
        blocking=True,
    )
    assert getattr(mock_instance, expected_call).call_count == 1
    getattr(mock_instance, expected_call).assert_called_once_with(*expected_args)


@pytest.mark.parametrize(
    ("service", "service_data", "request_method"),
    [
        (SERVICE_TURN_OFF, {}, "set_power_on"),
        (SERVICE_TURN_ON, {}, "set_power_on"),
        (SERVICE_SET_HVAC_MODE, {ATTR_HVAC_MODE: HVACMode.COOL}, "set_mode"),
        (SERVICE_SET_HVAC_MODE, {ATTR_HVAC_MODE: HVACMode.OFF}, "set_power_on"),
        (SERVICE_SET_TEMPERATURE, {ATTR_TEMPERATURE: 20}, "set_temp"),
        (SERVICE_SET_FAN_MODE, {ATTR_FAN_MODE: FAN_AUTO}, "set_fanspeed"),
        (SERVICE_SET_SWING_MODE, {ATTR_SWING_MODE: SWING_OFF}, "set_h_louver_swing"),
    ],
)
async def test_service_request_failure(
    hass: HomeAssistant,
    service: str,
    service_data: dict,
    request_method: str,
    multiple_climate_entities: tuple[str, str],
    request: pytest.FixtureRequest,
) -> None:
    """Test controlling the entity through service calls."""
    await init_integration(hass)
    entity_id, mock_fixture = multiple_climate_entities
    mock_instance = request.getfixturevalue(mock_fixture)
    getattr(mock_instance, request_method).return_value = False

    with pytest.raises(HomeAssistantError):
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            service,
            {ATTR_ENTITY_ID: entity_id, **service_data},
            blocking=True,
        )


@pytest.mark.parametrize(
    ("service", "service_data"),
    [
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.COOL},
        ),
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.HEAT},
        ),
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.FAN_ONLY},
        ),
    ],
)
async def test_service_hvac_mode_turn_on(
    hass: HomeAssistant,
    service: str,
    service_data: dict,
    multiple_climate_entities: tuple[str, str],
    request: pytest.FixtureRequest,
) -> None:
    """Test that the HVAC mode service call turns on the entity, if it is off."""
    await init_integration(hass)
    entity_id, mock_fixture = multiple_climate_entities
    mock_instance = request.getfixturevalue(mock_fixture)

    mock_instance.get_power_on.return_value = False
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        service,
        {ATTR_ENTITY_ID: entity_id, **service_data},
        blocking=True,
    )
    mock_instance.set_power_on.assert_called_once_with(True)

    # Test that set_power_on is not called if the device is already on
    mock_instance.set_power_on.reset_mock()
    mock_instance.get_power_on.return_value = True

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        service,
        {ATTR_ENTITY_ID: entity_id, **service_data},
        blocking=True,
    )
    mock_instance.set_power_on.assert_not_called()


@pytest.mark.parametrize(
    ("service", "service_data", "exception"),
    [
        (
            SERVICE_SET_HVAC_MODE,
            {ATTR_HVAC_MODE: HVACMode.DRY},
            ServiceValidationError,
        ),
        (
            SERVICE_SET_FAN_MODE,
            {ATTR_FAN_MODE: FAN_MIDDLE},
            ServiceValidationError,
        ),
    ],
)
async def test_service_unsupported(
    hass: HomeAssistant,
    service: str,
    service_data: dict,
    exception: type[Exception],
    multiple_climate_entities: tuple[str, str],
) -> None:
    """Test that unsupported service calls are handled properly."""
    await init_integration(hass)
    entity_id, _ = multiple_climate_entities

    with pytest.raises(exception):
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            service,
            {ATTR_ENTITY_ID: entity_id, **service_data},
            blocking=True,
        )


async def test_availability_logs(
    hass: HomeAssistant,
    caplog: pytest.LogCaptureFixture,
    multiple_climate_entities: tuple[str, str],
    request: pytest.FixtureRequest,
) -> None:
    """Test that availability status changes are logged correctly."""
    entity_id, mock_fixture = multiple_climate_entities
    mock_instance = request.getfixturevalue(mock_fixture)
    await init_integration(hass)

    caplog.clear()
    mock_instance.get_online.return_value = True
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state != STATE_UNAVAILABLE

    # Make the entity go offline - should log unavailable message
    mock_instance.get_online.return_value = False
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == STATE_UNAVAILABLE
    unavailable_log = f"The entity {entity_id} is unavailable"
    assert unavailable_log in caplog.text

    # Clear logs and update the offline entity again - should NOT log again
    caplog.clear()
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert unavailable_log not in caplog.text

    # Now bring the entity back online - should log back online message
    mock_instance.get_online.return_value = True
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state != STATE_UNAVAILABLE
    available_log = f"The entity {entity_id} is back online"
    assert available_log in caplog.text

    # Clear logs and make update again - should NOT log again
    caplog.clear()
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert available_log not in caplog.text

    # Test offline again to ensure the flag resets properly
    mock_instance.get_online.return_value = False
    state = await update_ac_state(hass, entity_id, mock_instance)
    assert state.state == STATE_UNAVAILABLE
    assert unavailable_log in caplog.text
