"""Tests for the BSB-Lan climate platform."""

from datetime import timedelta
from unittest.mock import AsyncMock, MagicMock

from bsblan import BSBLANError
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy.assertion import SnapshotAssertion

from homeassistant.components.climate import (
    ATTR_HVAC_MODE,
    ATTR_PRESET_MODE,
    DOMAIN as CLIMATE_DOMAIN,
    PRESET_ECO,
    PRESET_NONE,
    SERVICE_SET_HVAC_MODE,
    SERVICE_SET_PRESET_MODE,
    SERVICE_SET_TEMPERATURE,
    HVACMode,
)
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er

from . import setup_with_selected_platforms

from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform

ENTITY_ID = "climate.bsb_lan"


async def test_celsius_fahrenheit(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test Celsius and Fahrenheit temperature units."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)


async def test_climate_entity_properties(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    snapshot: SnapshotAssertion,
    entity_registry: er.EntityRegistry,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test the climate entity properties."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
    await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)

    # Test target_temperature
    mock_target_temp = MagicMock()
    mock_target_temp.value = 23.5
    mock_bsblan.state.return_value.target_temperature = mock_target_temp

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)
    assert state.attributes["temperature"] == 23.5

    # Test hvac_mode
    mock_hvac_mode = MagicMock()
    mock_hvac_mode.value = HVACMode.AUTO
    mock_bsblan.state.return_value.hvac_mode = mock_hvac_mode

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)
    assert state.state == HVACMode.AUTO

    # Test preset_mode
    mock_hvac_mode.value = PRESET_ECO

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    state = hass.states.get(ENTITY_ID)
    assert state.attributes["preset_mode"] == PRESET_ECO


async def test_climate_without_current_temperature_sensor(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test climate entity when current temperature sensor is not available."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    # Set current_temperature to None to simulate no temperature sensor
    mock_bsblan.state.return_value.current_temperature = None

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    # Should not crash and current_temperature should be None in attributes
    state = hass.states.get(ENTITY_ID)
    assert state is not None
    assert state.attributes["current_temperature"] is None


async def test_climate_without_target_temperature_sensor(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    freezer: FrozenDateTimeFactory,
) -> None:
    """Test climate entity when target temperature sensor is not available."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    # Set target_temperature to None to simulate no temperature sensor
    mock_bsblan.state.return_value.target_temperature = None

    freezer.tick(timedelta(minutes=1))
    async_fire_time_changed(hass)
    await hass.async_block_till_done()

    # Should not crash and target temperature should be None in attributes
    state = hass.states.get(ENTITY_ID)
    assert state is not None
    assert state.attributes["temperature"] is None


@pytest.mark.parametrize(
    "mode",
    [HVACMode.HEAT, HVACMode.AUTO, HVACMode.OFF],
)
async def test_async_set_hvac_mode(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    mode: HVACMode,
) -> None:
    """Test setting HVAC mode via service call."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    # Call the service to set HVAC mode
    await hass.services.async_call(
        domain=CLIMATE_DOMAIN,
        service=SERVICE_SET_HVAC_MODE,
        service_data={ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: mode},
        blocking=True,
    )

    # Assert that the thermostat method was called
    mock_bsblan.thermostat.assert_called_once_with(hvac_mode=mode)
    mock_bsblan.thermostat.reset_mock()


@pytest.mark.parametrize(
    ("hvac_mode", "preset_mode"),
    [
        (HVACMode.AUTO, PRESET_ECO),
        (HVACMode.AUTO, PRESET_NONE),
    ],
)
async def test_async_set_preset_mode_succes(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    hvac_mode: HVACMode,
    preset_mode: str,
) -> None:
    """Test setting preset mode via service call."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    # patch hvac_mode
    mock_hvac_mode = MagicMock()
    mock_hvac_mode.value = hvac_mode
    mock_bsblan.state.return_value.hvac_mode = mock_hvac_mode

    # Attempt to set the preset mode
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: preset_mode},
        blocking=True,
    )
    await hass.async_block_till_done()


@pytest.mark.parametrize(
    ("hvac_mode", "preset_mode"),
    [
        (
            HVACMode.HEAT,
            PRESET_ECO,
        )
    ],
)
async def test_async_set_preset_mode_error(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    hvac_mode: HVACMode,
    preset_mode: str,
) -> None:
    """Test setting preset mode via service call."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    # patch hvac_mode
    mock_hvac_mode = MagicMock()
    mock_hvac_mode.value = hvac_mode
    mock_bsblan.state.return_value.hvac_mode = mock_hvac_mode

    # Attempt to set the preset mode
    error_message = "Preset mode can only be set when HVAC mode is set to 'auto'"
    with pytest.raises(HomeAssistantError, match=error_message):
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_PRESET_MODE,
            {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: preset_mode},
            blocking=True,
        )


@pytest.mark.parametrize(
    ("target_temp"),
    [
        (8.0),  # Min temperature
        (15.0),  # Mid-range temperature
        (20.0),  # Max temperature
    ],
)
async def test_async_set_temperature(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
    target_temp: float,
) -> None:
    """Test setting temperature via service call."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    await hass.services.async_call(
        domain=CLIMATE_DOMAIN,
        service=SERVICE_SET_TEMPERATURE,
        service_data={ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: target_temp},
        blocking=True,
    )
    # Assert that the thermostat method was called with the correct temperature
    mock_bsblan.thermostat.assert_called_once_with(target_temperature=target_temp)


async def test_async_set_data(
    hass: HomeAssistant,
    mock_bsblan: AsyncMock,
    mock_config_entry: MockConfigEntry,
) -> None:
    """Test setting data via service calls."""
    await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])

    # Test setting temperature
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_TEMPERATURE,
        {ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 19},
        blocking=True,
    )
    mock_bsblan.thermostat.assert_called_once_with(target_temperature=19)
    mock_bsblan.thermostat.reset_mock()

    # Test setting HVAC mode
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_HVAC_MODE,
        {ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: HVACMode.HEAT},
        blocking=True,
    )
    mock_bsblan.thermostat.assert_called_once_with(hvac_mode=HVACMode.HEAT)
    mock_bsblan.thermostat.reset_mock()

    # Patch HVAC mode to AUTO
    mock_hvac_mode = MagicMock()
    mock_hvac_mode.value = HVACMode.AUTO
    mock_bsblan.state.return_value.hvac_mode = mock_hvac_mode

    # Test setting preset mode to ECO
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_ECO},
        blocking=True,
    )
    mock_bsblan.thermostat.assert_called_once_with(hvac_mode=PRESET_ECO)
    mock_bsblan.thermostat.reset_mock()

    # Test setting preset mode to NONE
    await hass.services.async_call(
        CLIMATE_DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: PRESET_NONE},
        blocking=True,
    )
    mock_bsblan.thermostat.assert_called_once()
    mock_bsblan.thermostat.reset_mock()

    # Test error handling
    mock_bsblan.thermostat.side_effect = BSBLANError("Test error")
    error_message = "An error occurred while updating the BSBLAN device"
    with pytest.raises(HomeAssistantError, match=error_message):
        await hass.services.async_call(
            CLIMATE_DOMAIN,
            SERVICE_SET_TEMPERATURE,
            {ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 20},
            blocking=True,
        )
