"""Test ZHA climate."""

from collections.abc import Callable, Coroutine
from typing import Literal
from unittest.mock import patch

import pytest
from zha.application.platforms.climate.const import HVAC_MODE_2_SYSTEM, SEQ_OF_OPERATION
import zhaquirks.sinope.thermostat
import zhaquirks.tuya.ts0601_trv
from zigpy.device import Device
import zigpy.profiles
from zigpy.profiles import zha
import zigpy.types
import zigpy.zcl.clusters
from zigpy.zcl.clusters.hvac import Thermostat

from homeassistant.components.climate import (
    ATTR_CURRENT_TEMPERATURE,
    ATTR_FAN_MODE,
    ATTR_FAN_MODES,
    ATTR_HVAC_ACTION,
    ATTR_HVAC_MODE,
    ATTR_HVAC_MODES,
    ATTR_PRESET_MODE,
    ATTR_TARGET_TEMP_HIGH,
    ATTR_TARGET_TEMP_LOW,
    DOMAIN as CLIMATE_DOMAIN,
    FAN_AUTO,
    FAN_LOW,
    FAN_ON,
    PRESET_AWAY,
    SERVICE_SET_FAN_MODE,
    SERVICE_SET_HVAC_MODE,
    SERVICE_SET_PRESET_MODE,
    SERVICE_SET_TEMPERATURE,
    HVACAction,
    HVACMode,
)
from homeassistant.components.zha.helpers import (
    ZHAGatewayProxy,
    get_zha_gateway,
    get_zha_gateway_proxy,
)
from homeassistant.const import (
    ATTR_ENTITY_ID,
    ATTR_TEMPERATURE,
    STATE_UNKNOWN,
    Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError

from .common import find_entity_id, send_attributes_report
from .conftest import SIG_EP_INPUT, SIG_EP_OUTPUT, SIG_EP_PROFILE, SIG_EP_TYPE

CLIMATE = {
    1: {
        SIG_EP_PROFILE: zha.PROFILE_ID,
        SIG_EP_TYPE: zha.DeviceType.THERMOSTAT,
        SIG_EP_INPUT: [
            zigpy.zcl.clusters.general.Basic.cluster_id,
            zigpy.zcl.clusters.general.Identify.cluster_id,
            zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
            zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
        ],
        SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id],
    }
}

CLIMATE_FAN = {
    1: {
        SIG_EP_PROFILE: zha.PROFILE_ID,
        SIG_EP_TYPE: zha.DeviceType.THERMOSTAT,
        SIG_EP_INPUT: [
            zigpy.zcl.clusters.general.Basic.cluster_id,
            zigpy.zcl.clusters.general.Identify.cluster_id,
            zigpy.zcl.clusters.hvac.Fan.cluster_id,
            zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
            zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
        ],
        SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id],
    }
}

CLIMATE_SINOPE = {
    1: {
        SIG_EP_PROFILE: zigpy.profiles.zha.PROFILE_ID,
        SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
        SIG_EP_INPUT: [
            zigpy.zcl.clusters.general.Basic.cluster_id,
            zigpy.zcl.clusters.general.Identify.cluster_id,
            zigpy.zcl.clusters.hvac.Thermostat.cluster_id,
            zigpy.zcl.clusters.hvac.UserInterface.cluster_id,
            65281,
        ],
        SIG_EP_OUTPUT: [zigpy.zcl.clusters.general.Ota.cluster_id, 65281],
    },
    196: {
        SIG_EP_PROFILE: 0xC25D,
        SIG_EP_TYPE: zigpy.profiles.zha.DeviceType.THERMOSTAT,
        SIG_EP_INPUT: [zigpy.zcl.clusters.general.PowerConfiguration.cluster_id],
        SIG_EP_OUTPUT: [],
    },
}

MANUF_SINOPE = "Sinope Technologies"

ZCL_ATTR_PLUG = {
    "abs_min_heat_setpoint_limit": 800,
    "abs_max_heat_setpoint_limit": 3000,
    "abs_min_cool_setpoint_limit": 2000,
    "abs_max_cool_setpoint_limit": 4000,
    "ctrl_sequence_of_oper": Thermostat.ControlSequenceOfOperation.Cooling_and_Heating,
    "local_temperature": None,
    "max_cool_setpoint_limit": 3900,
    "max_heat_setpoint_limit": 2900,
    "min_cool_setpoint_limit": 2100,
    "min_heat_setpoint_limit": 700,
    "occupancy": 1,
    "occupied_cooling_setpoint": 2500,
    "occupied_heating_setpoint": 2200,
    "pi_cooling_demand": None,
    "pi_heating_demand": None,
    "running_mode": Thermostat.RunningMode.Off,
    "running_state": None,
    "system_mode": Thermostat.SystemMode.Off,
    "unoccupied_heating_setpoint": 2200,
    "unoccupied_cooling_setpoint": 2300,
}


@pytest.fixture(autouse=True)
def climate_platform_only():
    """Only set up the climate and required base platforms to speed up tests."""
    with patch(
        "homeassistant.components.zha.PLATFORMS",
        (
            Platform.BUTTON,
            Platform.CLIMATE,
            Platform.BINARY_SENSOR,
            Platform.NUMBER,
            Platform.SELECT,
            Platform.SENSOR,
            Platform.SWITCH,
        ),
    ):
        yield


@pytest.fixture
def device_climate_mock(
    hass: HomeAssistant,
    setup_zha: Callable[..., Coroutine[None]],
    zigpy_device_mock: Callable[..., Device],
):
    """Test regular thermostat device."""

    async def _dev(clusters, plug=None, manuf=None, quirk=None):
        plugged_attrs = ZCL_ATTR_PLUG if plug is None else {**ZCL_ATTR_PLUG, **plug}
        zigpy_device = zigpy_device_mock(clusters, manufacturer=manuf, quirk=quirk)
        zigpy_device.node_desc.mac_capability_flags |= 0b_0000_0100
        zigpy_device.endpoints[1].thermostat.PLUGGED_ATTR_READS = plugged_attrs

        await setup_zha()
        gateway = get_zha_gateway(hass)
        gateway_proxy: ZHAGatewayProxy = get_zha_gateway_proxy(hass)
        gateway.get_or_create_device(zigpy_device)
        await gateway.async_device_initialized(zigpy_device)
        await hass.async_block_till_done(wait_background_tasks=True)
        return gateway_proxy.get_device_proxy(zigpy_device.ieee)

    return _dev


@pytest.fixture
async def device_climate(device_climate_mock):
    """Plain Climate device."""

    return await device_climate_mock(CLIMATE)


@pytest.fixture
async def device_climate_fan(device_climate_mock):
    """Test thermostat with fan device."""

    return await device_climate_mock(CLIMATE_FAN)


@pytest.fixture
@patch.object(
    zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
    "ep_attribute",
    "sinope_manufacturer_specific",
)
async def device_climate_sinope(device_climate_mock):
    """Sinope thermostat."""

    return await device_climate_mock(
        CLIMATE_SINOPE,
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )


def test_sequence_mappings() -> None:
    """Test correct mapping between control sequence -> HVAC Mode -> Sysmode."""

    for hvac_modes in SEQ_OF_OPERATION.values():
        for hvac_mode in hvac_modes:
            assert hvac_mode in HVAC_MODE_2_SYSTEM
            assert Thermostat.SystemMode(HVAC_MODE_2_SYSTEM[hvac_mode]) is not None


async def test_climate_local_temperature(hass: HomeAssistant, device_climate) -> None:
    """Test local temperature."""

    thrm_cluster = device_climate.device.device.endpoints[1].thermostat
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_CURRENT_TEMPERATURE] is None

    await send_attributes_report(hass, thrm_cluster, {0: 2100})
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 21.0


async def test_climate_hvac_action_running_state(
    hass: HomeAssistant, device_climate_sinope
) -> None:
    """Test hvac action via running state."""

    thrm_cluster = device_climate_sinope.device.device.endpoints[1].thermostat
    entity_id = find_entity_id(Platform.CLIMATE, device_climate_sinope, hass)
    sensor_entity_id = find_entity_id(
        Platform.SENSOR, device_climate_sinope, hass, "hvac"
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.OFF

    await send_attributes_report(
        hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Off}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.OFF

    await send_attributes_report(
        hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Auto}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.IDLE

    await send_attributes_report(
        hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Cool}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.COOLING

    await send_attributes_report(
        hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Heat}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.HEATING

    await send_attributes_report(
        hass, thrm_cluster, {0x001E: Thermostat.RunningMode.Off}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.IDLE

    await send_attributes_report(
        hass, thrm_cluster, {0x0029: Thermostat.RunningState.Fan_State_On}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.FAN
    hvac_sensor_state = hass.states.get(sensor_entity_id)
    assert hvac_sensor_state.state == HVACAction.FAN


async def test_climate_hvac_action_pi_demand(
    hass: HomeAssistant, device_climate
) -> None:
    """Test hvac action based on pi_heating/cooling_demand attrs."""

    thrm_cluster = device_climate.device.device.endpoints[1].thermostat
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)

    state = hass.states.get(entity_id)
    assert ATTR_HVAC_ACTION not in state.attributes

    await send_attributes_report(hass, thrm_cluster, {0x0007: 10})
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.COOLING

    await send_attributes_report(hass, thrm_cluster, {0x0008: 20})
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.HEATING

    await send_attributes_report(hass, thrm_cluster, {0x0007: 0})
    await send_attributes_report(hass, thrm_cluster, {0x0008: 0})

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.OFF

    await send_attributes_report(
        hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Heat}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE

    await send_attributes_report(
        hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Cool}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_HVAC_ACTION] == HVACAction.IDLE


@pytest.mark.parametrize(
    ("sys_mode", "hvac_mode"),
    [
        (Thermostat.SystemMode.Auto, HVACMode.HEAT_COOL),
        (Thermostat.SystemMode.Cool, HVACMode.COOL),
        (Thermostat.SystemMode.Heat, HVACMode.HEAT),
        (Thermostat.SystemMode.Pre_cooling, HVACMode.COOL),
        (Thermostat.SystemMode.Fan_only, HVACMode.FAN_ONLY),
        (Thermostat.SystemMode.Dry, HVACMode.DRY),
    ],
)
async def test_hvac_mode(
    hass: HomeAssistant, device_climate, sys_mode, hvac_mode
) -> None:
    """Test HVAC mode."""

    thrm_cluster = device_climate.device.device.endpoints[1].thermostat
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.OFF

    await send_attributes_report(hass, thrm_cluster, {0x001C: sys_mode})
    state = hass.states.get(entity_id)
    assert state.state == hvac_mode

    await send_attributes_report(
        hass, thrm_cluster, {0x001C: Thermostat.SystemMode.Off}
    )
    state = hass.states.get(entity_id)
    assert state.state == HVACMode.OFF

    await send_attributes_report(hass, thrm_cluster, {0x001C: 0xFF})
    state = hass.states.get(entity_id)
    assert state.state == STATE_UNKNOWN


@pytest.mark.parametrize(
    ("seq_of_op", "modes"),
    [
        (0xFF, {HVACMode.OFF}),
        (0x00, {HVACMode.OFF, HVACMode.COOL}),
        (0x01, {HVACMode.OFF, HVACMode.COOL}),
        (0x02, {HVACMode.OFF, HVACMode.HEAT}),
        (0x03, {HVACMode.OFF, HVACMode.HEAT}),
        (0x04, {HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL}),
        (0x05, {HVACMode.OFF, HVACMode.COOL, HVACMode.HEAT, HVACMode.HEAT_COOL}),
    ],
)
async def test_hvac_modes(
    hass: HomeAssistant, device_climate_mock, seq_of_op, modes
) -> None:
    """Test HVAC modes from sequence of operations."""

    device_climate = await device_climate_mock(
        CLIMATE, {"ctrl_sequence_of_oper": seq_of_op}
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    state = hass.states.get(entity_id)
    assert set(state.attributes[ATTR_HVAC_MODES]) == modes


@pytest.mark.parametrize(
    ("sys_mode", "preset", "target_temp"),
    [
        (Thermostat.SystemMode.Heat, None, 22),
        (Thermostat.SystemMode.Heat, PRESET_AWAY, 16),
        (Thermostat.SystemMode.Cool, None, 25),
        (Thermostat.SystemMode.Cool, PRESET_AWAY, 27),
    ],
)
async def test_target_temperature(
    hass: HomeAssistant,
    device_climate_mock,
    sys_mode: Thermostat.SystemMode,
    preset: Literal[PRESET_AWAY] | None,
    target_temp: int,
) -> None:
    """Test target temperature property."""

    device_climate = await device_climate_mock(
        CLIMATE_SINOPE,
        {
            "occupied_cooling_setpoint": 2500,
            "occupied_heating_setpoint": 2200,
            "system_mode": sys_mode,
            "unoccupied_heating_setpoint": 1600,
            "unoccupied_cooling_setpoint": 2700,
        },
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    if preset:
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_PRESET_MODE,
            {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset},
            blocking=True,
        )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TEMPERATURE] == target_temp


@pytest.mark.parametrize(
    ("preset", "unoccupied", "target_temp"),
    [
        (None, 1800, 17),
        (PRESET_AWAY, 1800, 18),
        (PRESET_AWAY, None, None),
    ],
)
async def test_target_temperature_high(
    hass: HomeAssistant, device_climate_mock, preset, unoccupied, target_temp
) -> None:
    """Test target temperature high property."""

    device_climate = await device_climate_mock(
        CLIMATE_SINOPE,
        {
            "occupied_cooling_setpoint": 1700,
            "system_mode": Thermostat.SystemMode.Auto,
            "unoccupied_cooling_setpoint": unoccupied,
        },
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    if preset:
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_PRESET_MODE,
            {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset},
            blocking=True,
        )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] == target_temp


@pytest.mark.parametrize(
    ("preset", "unoccupied", "target_temp"),
    [
        (None, 1600, 21),
        (PRESET_AWAY, 1600, 16),
        (PRESET_AWAY, None, None),
    ],
)
async def test_target_temperature_low(
    hass: HomeAssistant, device_climate_mock, preset, unoccupied, target_temp
) -> None:
    """Test target temperature low property."""

    device_climate = await device_climate_mock(
        CLIMATE_SINOPE,
        {
            "occupied_heating_setpoint": 2100,
            "system_mode": Thermostat.SystemMode.Auto,
            "unoccupied_heating_setpoint": unoccupied,
        },
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    if preset:
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_PRESET_MODE,
            {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: preset},
            blocking=True,
        )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] == target_temp


@pytest.mark.parametrize(
    ("hvac_mode", "sys_mode"),
    [
        (HVACMode.AUTO, None),
        (HVACMode.COOL, Thermostat.SystemMode.Cool),
        (HVACMode.DRY, None),
        (HVACMode.FAN_ONLY, None),
        (HVACMode.HEAT, Thermostat.SystemMode.Heat),
        (HVACMode.HEAT_COOL, Thermostat.SystemMode.Auto),
    ],
)
async def test_set_hvac_mode(
    hass: HomeAssistant, device_climate, hvac_mode, sys_mode
) -> None:
    """Test setting hvac mode."""

    thrm_cluster = device_climate.device.device.endpoints[1].thermostat
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.OFF

    if sys_mode is not None:
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_HVAC_MODE,
            {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode},
            blocking=True,
        )
        state = hass.states.get(entity_id)
        assert state.state == hvac_mode
        assert thrm_cluster.write_attributes.call_count == 1
        assert thrm_cluster.write_attributes.call_args[0][0] == {
            "system_mode": sys_mode
        }
    else:
        with pytest.raises(ServiceValidationError):
            await hass.services.async_call(
                CLIMATE_DOMAIN,
                SERVICE_SET_HVAC_MODE,
                {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: hvac_mode},
                blocking=True,
            )
        state = hass.states.get(entity_id)
        assert thrm_cluster.write_attributes.call_count == 0
        assert state.state == HVACMode.OFF

    # turn off
    thrm_cluster.write_attributes.reset_mock()
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_HVAC_MODE,
        {ATTR_ENTITY_ID: entity_id, ATTR_HVAC_MODE: HVACMode.OFF},
        blocking=True,
    )
    state = hass.states.get(entity_id)
    assert state.state == HVACMode.OFF
    assert thrm_cluster.write_attributes.call_count == 1
    assert thrm_cluster.write_attributes.call_args[0][0] == {
        "system_mode": Thermostat.SystemMode.Off
    }


async def test_set_temperature_hvac_mode(hass: HomeAssistant, device_climate) -> None:
    """Test setting HVAC mode in temperature service call."""

    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    thrm_cluster = device_climate.device.device.endpoints[1].thermostat

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.OFF

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_HVAC_MODE: HVACMode.HEAT_COOL,
            ATTR_TEMPERATURE: 20,
        },
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.HEAT_COOL
    assert thrm_cluster.write_attributes.await_count == 1
    assert thrm_cluster.write_attributes.call_args[0][0] == {
        "system_mode": Thermostat.SystemMode.Auto
    }


async def test_set_temperature_heat_cool(
    hass: HomeAssistant, device_climate_mock
) -> None:
    """Test setting temperature service call in heating/cooling HVAC mode."""

    device_climate = await device_climate_mock(
        CLIMATE_SINOPE,
        {
            "occupied_cooling_setpoint": 2500,
            "occupied_heating_setpoint": 2000,
            "system_mode": Thermostat.SystemMode.Auto,
            "unoccupied_heating_setpoint": 1600,
            "unoccupied_cooling_setpoint": 2700,
        },
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    thrm_cluster = device_climate.device.device.endpoints[1].thermostat

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.HEAT_COOL

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21},
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] == 20.0
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 25.0
    assert thrm_cluster.write_attributes.await_count == 0

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TARGET_TEMP_HIGH: 26,
            ATTR_TARGET_TEMP_LOW: 19,
        },
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] == 19.0
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 26.0
    assert thrm_cluster.write_attributes.await_count == 2
    assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
        "occupied_heating_setpoint": 1900
    }
    assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
        "occupied_cooling_setpoint": 2600
    }

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
        blocking=True,
    )
    thrm_cluster.write_attributes.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TARGET_TEMP_HIGH: 30,
            ATTR_TARGET_TEMP_LOW: 15,
        },
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] == 15.0
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] == 30.0
    assert thrm_cluster.write_attributes.await_count == 2
    assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
        "unoccupied_heating_setpoint": 1500
    }
    assert thrm_cluster.write_attributes.call_args_list[1][0][0] == {
        "unoccupied_cooling_setpoint": 3000
    }


async def test_set_temperature_heat(hass: HomeAssistant, device_climate_mock) -> None:
    """Test setting temperature service call in heating HVAC mode."""

    device_climate = await device_climate_mock(
        CLIMATE_SINOPE,
        {
            "occupied_cooling_setpoint": 2500,
            "occupied_heating_setpoint": 2000,
            "system_mode": Thermostat.SystemMode.Heat,
            "unoccupied_heating_setpoint": 1600,
            "unoccupied_cooling_setpoint": 2700,
        },
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    thrm_cluster = device_climate.device.device.endpoints[1].thermostat

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.HEAT

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TARGET_TEMP_HIGH: 30,
            ATTR_TARGET_TEMP_LOW: 15,
        },
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] == 20.0
    assert thrm_cluster.write_attributes.await_count == 0

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21},
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] == 21.0
    assert thrm_cluster.write_attributes.await_count == 1
    assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
        "occupied_heating_setpoint": 2100
    }

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
        blocking=True,
    )
    thrm_cluster.write_attributes.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22},
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] == 22.0
    assert thrm_cluster.write_attributes.await_count == 1
    assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
        "unoccupied_heating_setpoint": 2200
    }


async def test_set_temperature_cool(hass: HomeAssistant, device_climate_mock) -> None:
    """Test setting temperature service call in cooling HVAC mode."""

    device_climate = await device_climate_mock(
        CLIMATE_SINOPE,
        {
            "occupied_cooling_setpoint": 2500,
            "occupied_heating_setpoint": 2000,
            "system_mode": Thermostat.SystemMode.Cool,
            "unoccupied_cooling_setpoint": 1600,
            "unoccupied_heating_setpoint": 2700,
        },
        manuf=MANUF_SINOPE,
        quirk=zhaquirks.sinope.thermostat.SinopeTechnologiesThermostat,
    )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    thrm_cluster = device_climate.device.device.endpoints[1].thermostat

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

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TARGET_TEMP_HIGH: 30,
            ATTR_TARGET_TEMP_LOW: 15,
        },
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] == 25.0
    assert thrm_cluster.write_attributes.await_count == 0

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 21},
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] == 21.0
    assert thrm_cluster.write_attributes.await_count == 1
    assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
        "occupied_cooling_setpoint": 2100
    }

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: entity_id, ATTR_PRESET_MODE: PRESET_AWAY},
        blocking=True,
    )
    thrm_cluster.write_attributes.reset_mock()

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 22},
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] == 22.0
    assert thrm_cluster.write_attributes.await_count == 1
    assert thrm_cluster.write_attributes.call_args_list[0][0][0] == {
        "unoccupied_cooling_setpoint": 2200
    }


async def test_set_temperature_wrong_mode(
    hass: HomeAssistant, device_climate_mock
) -> None:
    """Test setting temperature service call for wrong HVAC mode."""

    with patch.object(
        zigpy.zcl.clusters.manufacturer_specific.ManufacturerSpecificCluster,
        "ep_attribute",
        "sinope_manufacturer_specific",
    ):
        device_climate = await device_climate_mock(
            CLIMATE_SINOPE,
            {
                "occupied_cooling_setpoint": 2500,
                "occupied_heating_setpoint": 2000,
                "system_mode": Thermostat.SystemMode.Dry,
                "unoccupied_cooling_setpoint": 1600,
                "unoccupied_heating_setpoint": 2700,
            },
            manuf=MANUF_SINOPE,
        )
    entity_id = find_entity_id(Platform.CLIMATE, device_climate, hass)
    thrm_cluster = device_climate.device.device.endpoints[1].thermostat

    state = hass.states.get(entity_id)
    assert state.state == HVACMode.DRY

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: entity_id, ATTR_TEMPERATURE: 24},
        blocking=True,
    )

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_TARGET_TEMP_LOW] is None
    assert state.attributes[ATTR_TARGET_TEMP_HIGH] is None
    assert state.attributes[ATTR_TEMPERATURE] is None
    assert thrm_cluster.write_attributes.await_count == 0


async def test_fan_mode(hass: HomeAssistant, device_climate_fan) -> None:
    """Test fan mode."""

    entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
    thrm_cluster = device_climate_fan.device.device.endpoints[1].thermostat

    state = hass.states.get(entity_id)
    assert set(state.attributes[ATTR_FAN_MODES]) == {FAN_AUTO, FAN_ON}
    assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO

    await send_attributes_report(
        hass, thrm_cluster, {"running_state": Thermostat.RunningState.Fan_State_On}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_FAN_MODE] == FAN_ON

    await send_attributes_report(
        hass, thrm_cluster, {"running_state": Thermostat.RunningState.Idle}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO

    await send_attributes_report(
        hass, thrm_cluster, {"running_state": Thermostat.RunningState.Fan_2nd_Stage_On}
    )
    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_FAN_MODE] == FAN_ON


async def test_set_fan_mode_not_supported(
    hass: HomeAssistant, device_climate_fan
) -> None:
    """Test fan setting unsupported mode."""

    entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
    fan_cluster = device_climate_fan.device.device.endpoints[1].fan

    with pytest.raises(ServiceValidationError):
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_FAN_MODE,
            {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_LOW},
            blocking=True,
        )
    assert fan_cluster.write_attributes.await_count == 0


async def test_set_fan_mode(hass: HomeAssistant, device_climate_fan) -> None:
    """Test fan mode setting."""

    entity_id = find_entity_id(Platform.CLIMATE, device_climate_fan, hass)
    fan_cluster = device_climate_fan.device.device.endpoints[1].fan

    state = hass.states.get(entity_id)
    assert state.attributes[ATTR_FAN_MODE] == FAN_AUTO

    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_FAN_MODE,
        {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_ON},
        blocking=True,
    )
    assert fan_cluster.write_attributes.await_count == 1
    assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 4}

    fan_cluster.write_attributes.reset_mock()
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_FAN_MODE,
        {ATTR_ENTITY_ID: entity_id, ATTR_FAN_MODE: FAN_AUTO},
        blocking=True,
    )
    assert fan_cluster.write_attributes.await_count == 1
    assert fan_cluster.write_attributes.call_args[0][0] == {"fan_mode": 5}
