"""Test the Nibe Heat Pump config flow."""

from typing import Any
from unittest.mock import call, patch

from nibe.coil import CoilData
from nibe.coil_groups import (
    CLIMATE_COILGROUPS,
    UNIT_COILGROUPS,
    ClimateCoilGroup,
    UnitCoilGroup,
)
from nibe.heatpump import Model
import pytest
from syrupy import SnapshotAssertion

from homeassistant.components.climate import (
    ATTR_HVAC_MODE,
    ATTR_TARGET_TEMP_HIGH,
    ATTR_TARGET_TEMP_LOW,
    ATTR_TEMPERATURE,
    DOMAIN as PLATFORM_DOMAIN,
    SERVICE_SET_HVAC_MODE,
    SERVICE_SET_TEMPERATURE,
    HVACMode,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError

from . import MockConnection, async_add_model


@pytest.fixture(autouse=True)
async def fixture_single_platform():
    """Only allow this platform to load."""
    with patch("homeassistant.components.nibe_heatpump.PLATFORMS", [Platform.CLIMATE]):
        yield


def _setup_climate_group(
    coils: dict[int, Any], model: Model, climate_id: str
) -> tuple[ClimateCoilGroup, UnitCoilGroup]:
    """Initialize coils for a climate group, with some default values."""
    climate = CLIMATE_COILGROUPS[model.series][climate_id]
    unit = UNIT_COILGROUPS[model.series]["main"]

    if climate.active_accessory is not None:
        coils[climate.active_accessory] = "ON"
    coils[climate.current] = 20.5
    coils[climate.setpoint_heat] = 21.0
    coils[climate.setpoint_cool] = 30.0
    coils[climate.mixing_valve_state] = 20
    coils[climate.use_room_sensor] = "ON"
    coils[unit.prio] = "OFF"
    coils[unit.cooling_with_room_sensor] = "ON"

    return climate, unit


@pytest.mark.parametrize(
    ("model", "climate_id", "entity_id"),
    [
        (Model.S320, "s1", "climate.climate_system_s1"),
        (Model.F1155, "s2", "climate.climate_system_s2"),
        (Model.F730, "s1", "climate.climate_system_s1"),
    ],
)
async def test_basic(
    hass: HomeAssistant,
    mock_connection: MockConnection,
    model: Model,
    climate_id: str,
    entity_id: str,
    coils: dict[int, Any],
    entity_registry_enabled_by_default: None,
    snapshot: SnapshotAssertion,
) -> None:
    """Test setting of value."""
    climate, unit = _setup_climate_group(coils, model, climate_id)

    await async_add_model(hass, model)

    assert hass.states.get(entity_id) == snapshot(name="initial")

    mock_connection.mock_coil_update(unit.prio, "COOLING")
    assert hass.states.get(entity_id) == snapshot(name="cooling")

    mock_connection.mock_coil_update(unit.prio, "HEAT")
    assert hass.states.get(entity_id) == snapshot(name="heating")

    mock_connection.mock_coil_update(climate.mixing_valve_state, 30)
    assert hass.states.get(entity_id) == snapshot(name="idle (mixing valve)")

    mock_connection.mock_coil_update(climate.mixing_valve_state, 20)
    mock_connection.mock_coil_update(unit.cooling_with_room_sensor, "OFF")
    assert hass.states.get(entity_id) == snapshot(name="heating (only)")

    mock_connection.mock_coil_update(climate.use_room_sensor, "OFF")
    assert hass.states.get(entity_id) == snapshot(name="heating (auto)")

    mock_connection.mock_coil_update(unit.prio, None)
    assert hass.states.get(entity_id) == snapshot(name="off (auto)")

    coils.clear()
    assert hass.states.get(entity_id) == snapshot(name="unavailable")


@pytest.mark.parametrize(
    ("model", "climate_id", "entity_id"),
    [
        (Model.F1155, "s2", "climate.climate_system_s2"),
        (Model.F1155, "s3", "climate.climate_system_s3"),
    ],
)
async def test_active_accessory(
    hass: HomeAssistant,
    mock_connection: MockConnection,
    model: Model,
    climate_id: str,
    entity_id: str,
    coils: dict[int, Any],
    entity_registry_enabled_by_default: None,
    snapshot: SnapshotAssertion,
) -> None:
    """Test climate groups that can be deactivated by configuration."""
    climate, unit = _setup_climate_group(coils, model, climate_id)

    await async_add_model(hass, model)

    assert hass.states.get(entity_id) == snapshot(name="initial")

    mock_connection.mock_coil_update(climate.active_accessory, "OFF")
    assert hass.states.get(entity_id) == snapshot(name="unavailable (not supported)")


@pytest.mark.parametrize(
    ("model", "climate_id", "entity_id"),
    [
        (Model.S320, "s1", "climate.climate_system_s1"),
        (Model.F1155, "s2", "climate.climate_system_s2"),
    ],
)
async def test_set_temperature_supported_cooling(
    hass: HomeAssistant,
    mock_connection: MockConnection,
    model: Model,
    climate_id: str,
    entity_id: str,
    coils: dict[int, Any],
    entity_registry_enabled_by_default: None,
    snapshot: SnapshotAssertion,
) -> None:
    """Test setting temperature for models with cooling support."""
    climate, _ = _setup_climate_group(coils, model, climate_id)

    await async_add_model(hass, model)

    coil_setpoint_heat = mock_connection.heatpump.get_coil_by_address(
        climate.setpoint_heat
    )
    coil_setpoint_cool = mock_connection.heatpump.get_coil_by_address(
        climate.setpoint_cool
    )

    await hass.services.async_call(
        PLATFORM_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TEMPERATURE: 22,
            ATTR_HVAC_MODE: HVACMode.HEAT,
        },
        blocking=True,
    )
    await hass.async_block_till_done()

    assert mock_connection.write_coil.mock_calls == [
        call(CoilData(coil_setpoint_heat, 22))
    ]
    mock_connection.write_coil.reset_mock()

    await hass.services.async_call(
        PLATFORM_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TEMPERATURE: 22,
            ATTR_HVAC_MODE: HVACMode.COOL,
        },
        blocking=True,
    )
    await hass.async_block_till_done()

    assert mock_connection.write_coil.mock_calls == [
        call(CoilData(coil_setpoint_cool, 22))
    ]
    mock_connection.write_coil.reset_mock()

    with pytest.raises(ServiceValidationError):
        await hass.services.async_call(
            PLATFORM_DOMAIN,
            SERVICE_SET_TEMPERATURE,
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_TEMPERATURE: 22,
            },
            blocking=True,
        )

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

    assert mock_connection.write_coil.mock_calls == [
        call(CoilData(coil_setpoint_heat, 22)),
        call(CoilData(coil_setpoint_cool, 30)),
    ]

    mock_connection.write_coil.reset_mock()


@pytest.mark.parametrize(
    ("model", "climate_id", "entity_id"),
    [
        (Model.F730, "s1", "climate.climate_system_s1"),
    ],
)
async def test_set_temperature_unsupported_cooling(
    hass: HomeAssistant,
    mock_connection: MockConnection,
    model: Model,
    climate_id: str,
    entity_id: str,
    coils: dict[int, Any],
    entity_registry_enabled_by_default: None,
    snapshot: SnapshotAssertion,
) -> None:
    """Test setting temperature for models that do not support cooling."""
    climate, _ = _setup_climate_group(coils, model, climate_id)

    await async_add_model(hass, model)

    coil_setpoint_heat = mock_connection.heatpump.get_coil_by_address(
        climate.setpoint_heat
    )

    # Set temperature to heat
    await hass.services.async_call(
        PLATFORM_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_TEMPERATURE: 22,
            ATTR_HVAC_MODE: HVACMode.HEAT,
        },
        blocking=True,
    )
    await hass.async_block_till_done()
    assert mock_connection.write_coil.mock_calls == [
        call(CoilData(coil_setpoint_heat, 22))
    ]

    # Attempt to set temperature to cool should raise ServiceValidationError
    with pytest.raises(ServiceValidationError):
        await hass.services.async_call(
            PLATFORM_DOMAIN,
            SERVICE_SET_TEMPERATURE,
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_TEMPERATURE: 22,
                ATTR_HVAC_MODE: HVACMode.COOL,
            },
            blocking=True,
        )
    mock_connection.write_coil.reset_mock()


@pytest.mark.parametrize(
    ("hvac_mode", "cooling_with_room_sensor", "use_room_sensor"),
    [
        (HVACMode.HEAT_COOL, "ON", "ON"),
        (HVACMode.HEAT, "OFF", "ON"),
        (HVACMode.AUTO, "OFF", "OFF"),
    ],
)
@pytest.mark.parametrize(
    ("model", "climate_id", "entity_id"),
    [
        (Model.S320, "s1", "climate.climate_system_s1"),
        (Model.F1155, "s2", "climate.climate_system_s2"),
        (Model.F730, "s1", "climate.climate_system_s1"),
    ],
)
async def test_set_hvac_mode(
    hass: HomeAssistant,
    mock_connection: MockConnection,
    model: Model,
    climate_id: str,
    entity_id: str,
    cooling_with_room_sensor: str,
    use_room_sensor: str,
    hvac_mode: HVACMode,
    coils: dict[int, Any],
    entity_registry_enabled_by_default: None,
) -> None:
    """Test setting a hvac mode."""
    climate, unit = _setup_climate_group(coils, model, climate_id)

    await async_add_model(hass, model)

    coil_use_room_sensor = mock_connection.heatpump.get_coil_by_address(
        climate.use_room_sensor
    )
    coil_cooling_with_room_sensor = mock_connection.heatpump.get_coil_by_address(
        unit.cooling_with_room_sensor
    )

    await hass.services.async_call(
        PLATFORM_DOMAIN,
        SERVICE_SET_HVAC_MODE,
        {
            ATTR_ENTITY_ID: entity_id,
            ATTR_HVAC_MODE: hvac_mode,
        },
        blocking=True,
    )
    await hass.async_block_till_done()

    assert mock_connection.write_coil.mock_calls == [
        call(CoilData(coil_cooling_with_room_sensor, cooling_with_room_sensor)),
        call(CoilData(coil_use_room_sensor, use_room_sensor)),
    ]


@pytest.mark.parametrize(
    ("model", "climate_id", "entity_id", "unsupported_mode"),
    [
        (Model.S320, "s1", "climate.climate_system_s1", HVACMode.DRY),
        (Model.F1155, "s2", "climate.climate_system_s2", HVACMode.DRY),
        (Model.F730, "s1", "climate.climate_system_s1", HVACMode.COOL),
    ],
)
async def test_set_invalid_hvac_mode(
    hass: HomeAssistant,
    mock_connection: MockConnection,
    model: Model,
    climate_id: str,
    entity_id: str,
    unsupported_mode: str,
    coils: dict[int, Any],
    entity_registry_enabled_by_default: None,
) -> None:
    """Test setting an invalid hvac mode."""
    _setup_climate_group(coils, model, climate_id)

    await async_add_model(hass, model)
    with pytest.raises(ServiceValidationError):
        await hass.services.async_call(
            PLATFORM_DOMAIN,
            SERVICE_SET_HVAC_MODE,
            {
                ATTR_ENTITY_ID: entity_id,
                ATTR_HVAC_MODE: unsupported_mode,
            },
            blocking=True,
        )
        await hass.async_block_till_done()

    assert mock_connection.write_coil.mock_calls == []
