"""Test ESPHome climates."""

import math
from unittest.mock import call

from aioesphomeapi import (
    APIClient,
    ClimateAction,
    ClimateFanMode,
    ClimateInfo,
    ClimateMode,
    ClimatePreset,
    ClimateState,
    ClimateSwingMode,
)
from syrupy import SnapshotAssertion

from homeassistant.components.climate import (
    ATTR_CURRENT_HUMIDITY,
    ATTR_CURRENT_TEMPERATURE,
    ATTR_FAN_MODE,
    ATTR_HUMIDITY,
    ATTR_HVAC_MODE,
    ATTR_MAX_HUMIDITY,
    ATTR_MIN_HUMIDITY,
    ATTR_PRESET_MODE,
    ATTR_SWING_MODE,
    ATTR_TARGET_TEMP_HIGH,
    ATTR_TARGET_TEMP_LOW,
    ATTR_TEMPERATURE,
    DOMAIN as CLIMATE_DOMAIN,
    FAN_HIGH,
    SERVICE_SET_FAN_MODE,
    SERVICE_SET_HUMIDITY,
    SERVICE_SET_HVAC_MODE,
    SERVICE_SET_PRESET_MODE,
    SERVICE_SET_SWING_MODE,
    SERVICE_SET_TEMPERATURE,
    SWING_BOTH,
    HVACMode,
)
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant


async def test_climate_entity(
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
) -> None:
    """Test a generic climate entity."""
    entity_info = [
        ClimateInfo(
            object_id="myclimate",
            key=1,
            name="my climate",
            unique_id="my_climate",
            supports_current_temperature=True,
            supports_two_point_target_temperature=True,
            supports_action=True,
            visual_min_temperature=10.0,
            visual_max_temperature=30.0,
        )
    ]
    states = [
        ClimateState(
            key=1,
            mode=ClimateMode.COOL,
            action=ClimateAction.COOLING,
            current_temperature=30,
            target_temperature=20,
            fan_mode=ClimateFanMode.AUTO,
            swing_mode=ClimateSwingMode.BOTH,
        )
    ]
    user_service = []
    await mock_generic_device_entry(
        mock_client=mock_client,
        entity_info=entity_info,
        user_service=user_service,
        states=states,
    )
    state = hass.states.get("climate.test_myclimate")
    assert state is not None
    assert state.state == HVACMode.COOL

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls([call(key=1, target_temperature=25.0)])
    mock_client.climate_command.reset_mock()


async def test_climate_entity_with_step_and_two_point(
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
) -> None:
    """Test a generic climate entity."""
    entity_info = [
        ClimateInfo(
            object_id="myclimate",
            key=1,
            name="my climate",
            unique_id="my_climate",
            supports_current_temperature=True,
            supports_two_point_target_temperature=True,
            visual_target_temperature_step=2,
            visual_current_temperature_step=2,
            supports_action=False,
            visual_min_temperature=10.0,
            visual_max_temperature=30.0,
            supported_modes=[ClimateMode.COOL, ClimateMode.HEAT, ClimateMode.AUTO],
            supported_presets=[ClimatePreset.AWAY, ClimatePreset.ACTIVITY],
        )
    ]
    states = [
        ClimateState(
            key=1,
            mode=ClimateMode.COOL,
            current_temperature=30,
            target_temperature=20,
            fan_mode=ClimateFanMode.AUTO,
            swing_mode=ClimateSwingMode.BOTH,
        )
    ]
    user_service = []
    await mock_generic_device_entry(
        mock_client=mock_client,
        entity_info=entity_info,
        user_service=user_service,
        states=states,
    )
    state = hass.states.get("climate.test_myclimate")
    assert state is not None
    assert state.state == HVACMode.COOL

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls([call(key=1, target_temperature=25.0)])
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: "climate.test_myclimate",
            ATTR_HVAC_MODE: HVACMode.AUTO,
            ATTR_TARGET_TEMP_LOW: 20,
            ATTR_TARGET_TEMP_HIGH: 30,
        },
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls(
        [
            call(
                key=1,
                mode=ClimateMode.AUTO,
                target_temperature_low=20.0,
                target_temperature_high=30.0,
            )
        ]
    )
    mock_client.climate_command.reset_mock()


async def test_climate_entity_with_step_and_target_temp(
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
) -> None:
    """Test a generic climate entity."""
    entity_info = [
        ClimateInfo(
            object_id="myclimate",
            key=1,
            name="my climate",
            unique_id="my_climate",
            supports_current_temperature=True,
            visual_target_temperature_step=2,
            visual_current_temperature_step=2,
            supports_action=True,
            visual_min_temperature=10.0,
            visual_max_temperature=30.0,
            supported_fan_modes=[ClimateFanMode.LOW, ClimateFanMode.HIGH],
            supported_modes=[ClimateMode.COOL, ClimateMode.HEAT, ClimateMode.AUTO],
            supported_presets=[ClimatePreset.AWAY, ClimatePreset.ACTIVITY],
            supported_custom_presets=["preset1", "preset2"],
            supported_custom_fan_modes=["fan1", "fan2"],
            supported_swing_modes=[ClimateSwingMode.BOTH, ClimateSwingMode.OFF],
        )
    ]
    states = [
        ClimateState(
            key=1,
            mode=ClimateMode.COOL,
            action=ClimateAction.COOLING,
            current_temperature=30,
            target_temperature=20,
            fan_mode=ClimateFanMode.AUTO,
            swing_mode=ClimateSwingMode.BOTH,
        )
    ]
    user_service = []
    await mock_generic_device_entry(
        mock_client=mock_client,
        entity_info=entity_info,
        user_service=user_service,
        states=states,
    )
    state = hass.states.get("climate.test_myclimate")
    assert state is not None
    assert state.state == HVACMode.COOL

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_TEMPERATURE: 25},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls([call(key=1, target_temperature=25.0)])
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: "climate.test_myclimate",
            ATTR_HVAC_MODE: HVACMode.AUTO,
            ATTR_TARGET_TEMP_LOW: 20,
            ATTR_TARGET_TEMP_HIGH: 30,
        },
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls(
        [
            call(
                key=1,
                mode=ClimateMode.AUTO,
                target_temperature_low=20.0,
                target_temperature_high=30.0,
            )
        ]
    )
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_HVAC_MODE,
        {
            ATTR_ENTITY_ID: "climate.test_myclimate",
            ATTR_HVAC_MODE: HVACMode.HEAT,
        },
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls(
        [
            call(
                key=1,
                mode=ClimateMode.HEAT,
            )
        ]
    )
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_PRESET_MODE: "away"},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls(
        [
            call(
                key=1,
                preset=ClimatePreset.AWAY,
            )
        ]
    )
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_PRESET_MODE: "preset1"},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls([call(key=1, custom_preset="preset1")])
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_FAN_MODE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_FAN_MODE: FAN_HIGH},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls(
        [call(key=1, fan_mode=ClimateFanMode.HIGH)]
    )
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_FAN_MODE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_FAN_MODE: "fan2"},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls([call(key=1, custom_fan_mode="fan2")])
    mock_client.climate_command.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_SWING_MODE,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_SWING_MODE: SWING_BOTH},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls(
        [call(key=1, swing_mode=ClimateSwingMode.BOTH)]
    )
    mock_client.climate_command.reset_mock()


async def test_climate_entity_with_humidity(
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
) -> None:
    """Test a generic climate entity with humidity."""
    entity_info = [
        ClimateInfo(
            object_id="myclimate",
            key=1,
            name="my climate",
            unique_id="my_climate",
            supports_current_temperature=True,
            supports_two_point_target_temperature=True,
            supports_action=True,
            visual_min_temperature=10.0,
            visual_max_temperature=30.0,
            supports_current_humidity=True,
            supports_target_humidity=True,
            visual_min_humidity=10.1,
            visual_max_humidity=29.7,
        )
    ]
    states = [
        ClimateState(
            key=1,
            mode=ClimateMode.AUTO,
            action=ClimateAction.COOLING,
            current_temperature=30,
            target_temperature=20,
            fan_mode=ClimateFanMode.AUTO,
            swing_mode=ClimateSwingMode.BOTH,
            current_humidity=20.1,
            target_humidity=25.7,
        )
    ]
    user_service = []
    await mock_generic_device_entry(
        mock_client=mock_client,
        entity_info=entity_info,
        user_service=user_service,
        states=states,
    )
    state = hass.states.get("climate.test_myclimate")
    assert state is not None
    assert state.state == HVACMode.AUTO
    attributes = state.attributes
    assert attributes[ATTR_CURRENT_HUMIDITY] == 20
    assert attributes[ATTR_HUMIDITY] == 26
    assert attributes[ATTR_MAX_HUMIDITY] == 30
    assert attributes[ATTR_MIN_HUMIDITY] == 10

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_HUMIDITY,
        {ATTR_ENTITY_ID: "climate.test_myclimate", ATTR_HUMIDITY: 23},
        blocking=True,
    )
    mock_client.climate_command.assert_has_calls([call(key=1, target_humidity=23)])
    mock_client.climate_command.reset_mock()


async def test_climate_entity_with_inf_value(
    hass: HomeAssistant, mock_client: APIClient, mock_generic_device_entry
) -> None:
    """Test a generic climate entity with infinite temp."""
    entity_info = [
        ClimateInfo(
            object_id="myclimate",
            key=1,
            name="my climate",
            unique_id="my_climate",
            supports_current_temperature=True,
            supports_two_point_target_temperature=True,
            supports_action=True,
            visual_min_temperature=10.0,
            visual_max_temperature=30.0,
            supports_current_humidity=True,
            supports_target_humidity=True,
            visual_min_humidity=10.1,
            visual_max_humidity=29.7,
        )
    ]
    states = [
        ClimateState(
            key=1,
            mode=ClimateMode.AUTO,
            action=ClimateAction.COOLING,
            current_temperature=math.inf,
            target_temperature=math.inf,
            fan_mode=ClimateFanMode.AUTO,
            swing_mode=ClimateSwingMode.BOTH,
            current_humidity=20.1,
            target_humidity=25.7,
        )
    ]
    user_service = []
    await mock_generic_device_entry(
        mock_client=mock_client,
        entity_info=entity_info,
        user_service=user_service,
        states=states,
    )
    state = hass.states.get("climate.test_myclimate")
    assert state is not None
    assert state.state == HVACMode.AUTO
    attributes = state.attributes
    assert attributes[ATTR_CURRENT_HUMIDITY] == 20
    assert attributes[ATTR_HUMIDITY] == 26
    assert attributes[ATTR_MAX_HUMIDITY] == 30
    assert attributes[ATTR_MIN_HUMIDITY] == 10
    assert ATTR_TEMPERATURE not in attributes
    assert attributes[ATTR_CURRENT_TEMPERATURE] is None


async def test_climate_entity_attributes(
    hass: HomeAssistant,
    mock_client: APIClient,
    mock_generic_device_entry,
    snapshot: SnapshotAssertion,
) -> None:
    """Test a climate entity sets correct attributes."""
    entity_info = [
        ClimateInfo(
            object_id="myclimate",
            key=1,
            name="my climate",
            unique_id="my_climate",
            supports_current_temperature=True,
            visual_target_temperature_step=2,
            visual_current_temperature_step=2,
            supports_action=True,
            visual_min_temperature=10.0,
            visual_max_temperature=30.0,
            supported_fan_modes=[ClimateFanMode.LOW, ClimateFanMode.HIGH],
            supported_modes=[
                ClimateMode.COOL,
                ClimateMode.HEAT,
                ClimateMode.AUTO,
                ClimateMode.OFF,
            ],
            supported_presets=[ClimatePreset.AWAY, ClimatePreset.ACTIVITY],
            supported_custom_presets=["preset1", "preset2"],
            supported_custom_fan_modes=["fan1", "fan2"],
            supported_swing_modes=[ClimateSwingMode.BOTH, ClimateSwingMode.OFF],
        )
    ]
    states = [
        ClimateState(
            key=1,
            mode=ClimateMode.COOL,
            action=ClimateAction.COOLING,
            current_temperature=30,
            target_temperature=20,
            fan_mode=ClimateFanMode.AUTO,
            swing_mode=ClimateSwingMode.BOTH,
        )
    ]
    user_service = []
    await mock_generic_device_entry(
        mock_client=mock_client,
        entity_info=entity_info,
        user_service=user_service,
        states=states,
    )
    state = hass.states.get("climate.test_myclimate")
    assert state is not None
    assert state.state == HVACMode.COOL
    assert state.attributes == snapshot(name="climate-entity-attributes")