# pylint: disable=wildcard-import, unused-wildcard-import, protected-access, unused-argument, line-too-long

""" Test the normal start of a Thermostat """
from unittest.mock import patch, call
from datetime import datetime, timedelta

from homeassistant.core import HomeAssistant
from homeassistant.components.climate import HVACAction, HVACMode
from homeassistant.config_entries import ConfigEntryState

from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components.climate import ClimateEntity, DOMAIN as CLIMATE_DOMAIN

from pytest_homeassistant_custom_component.common import MockConfigEntry

# from custom_components.versatile_thermostat.base_thermostat import BaseThermostat
from custom_components.versatile_thermostat.thermostat_climate import (
    ThermostatOverClimate,
)

from .commons import *  # pylint: disable=wildcard-import, unused-wildcard-import


@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_regulation(
    hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
):
    """Test the regulation of an over climate thermostat"""

    entry = MockConfigEntry(
        domain=DOMAIN,
        title="TheOverClimateMockName",
        unique_id="uniqueId",
        # This is include a medium regulation
        data=PARTIAL_CLIMATE_CONFIG,
    )

    tz = get_tz(hass)  # pylint: disable=invalid-name
    now: datetime = datetime.now(tz=tz)

    fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})

    # Creates the regulated VTherm over climate
    # change temperature so that the heating will start
    event_timestamp = now - timedelta(minutes=10)

    with patch(
        "custom_components.versatile_thermostat.const.NowClass.get_now",
        return_value=event_timestamp,
    ), patch(
        "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
        return_value=fake_underlying_climate,
    ):
        entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")

        assert entity
        assert isinstance(entity, ThermostatOverClimate)

        assert entity.name == "TheOverClimateMockName"
        assert entity.is_over_climate is True
        assert entity.is_regulated is True
        assert entity.auto_regulation_use_device_temp is False
        assert entity.hvac_mode is HVACMode.OFF
        assert entity.hvac_action is HVACAction.OFF
        assert entity.target_temperature == entity.min_temp
        assert entity.preset_modes == [
            PRESET_NONE,
            PRESET_FROST_PROTECTION,
            PRESET_ECO,
            PRESET_COMFORT,
            PRESET_BOOST,
        ]
        assert entity.preset_mode is PRESET_NONE

        # Activate the heating by changing HVACMode and temperature
        # Select a hvacmode, presence and preset
        await entity.async_set_hvac_mode(HVACMode.HEAT)
        assert entity.hvac_mode is HVACMode.HEAT
        assert entity.hvac_action == HVACAction.OFF

        assert entity.regulated_target_temp == entity.min_temp

        await send_temperature_change_event(entity, 15, event_timestamp)
        await send_ext_temperature_change_event(entity, 10, event_timestamp)

        # set manual target temp (at now - 7) -> the regulation should occurs
        event_timestamp = now - timedelta(minutes=7)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await entity.async_set_temperature(temperature=18)

            fake_underlying_climate.set_hvac_action(
                HVACAction.HEATING
            )  # simulate under heating
            assert entity.hvac_action == HVACAction.HEATING
            assert entity.preset_mode == PRESET_NONE  # Manual mode

            # the regulated temperature should be greater
            assert entity.regulated_target_temp > entity.target_temperature
            # In medium we could go up to +3 degre
            # normally the calcul gives 18 + 2.2 but we round the result to the nearest 0.5 which is 2.0
            assert entity.regulated_target_temp == 18 + 1.5
            assert entity.hvac_action == HVACAction.HEATING

        # change temperature so that the regulated temperature should slow down
        event_timestamp = now - timedelta(minutes=5)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await send_temperature_change_event(entity, 23, event_timestamp)
            await send_ext_temperature_change_event(entity, 19, event_timestamp)

            # the regulated temperature should be under
            assert entity.regulated_target_temp < entity.target_temperature
            assert entity.regulated_target_temp == 18 - 2.5


@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_regulation_ac_mode(
    hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
):
    """Test the regulation of an over climate thermostat"""

    entry = MockConfigEntry(
        domain=DOMAIN,
        title="TheOverClimateMockName",
        unique_id="uniqueId",
        # This is include a medium regulation
        data=PARTIAL_CLIMATE_AC_CONFIG,
    )

    tz = get_tz(hass)  # pylint: disable=invalid-name
    now: datetime = datetime.now(tz=tz)

    fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})

    # Creates the regulated VTherm over climate
    # change temperature so that the heating will start
    event_timestamp = now - timedelta(minutes=10)

    with patch(
        "custom_components.versatile_thermostat.const.NowClass.get_now",
        return_value=event_timestamp,
    ), patch(
        "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
        return_value=fake_underlying_climate,
    ):
        entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")

        assert entity
        assert isinstance(entity, ThermostatOverClimate)

        assert entity.name == "TheOverClimateMockName"
        assert entity.is_over_climate is True
        assert entity.is_regulated is True
        assert entity.hvac_action is HVACAction.OFF
        assert entity.hvac_mode is HVACMode.OFF
        assert entity.target_temperature == entity.max_temp
        assert entity.preset_modes == [
            PRESET_NONE,
            PRESET_FROST_PROTECTION,
            PRESET_ECO,
            PRESET_COMFORT,
            PRESET_BOOST,
        ]
        assert entity.preset_mode is PRESET_NONE

        # Activate the heating by changing HVACMode and temperature
        # Select a hvacmode, presence and preset
        await entity.async_set_hvac_mode(HVACMode.HEAT)
        assert entity.hvac_mode is HVACMode.HEAT
        assert entity.hvac_action == HVACAction.OFF

        # change temperature so that the heating will start
        await send_temperature_change_event(entity, 30, event_timestamp)
        await send_ext_temperature_change_event(entity, 35, event_timestamp)

        # set manual target temp
        event_timestamp = now - timedelta(minutes=7)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await entity.async_set_temperature(temperature=25)

            fake_underlying_climate.set_hvac_action(
                HVACAction.COOLING
            )  # simulate under heating
            assert entity.hvac_action == HVACAction.COOLING
            assert entity.preset_mode == PRESET_NONE  # Manual mode

            # the regulated temperature should be lower
            assert entity.regulated_target_temp < entity.target_temperature
            assert (
                entity.regulated_target_temp == 25 - 2.5
            )  # In medium we could go up to -3 degre
            assert entity.hvac_action == HVACAction.COOLING

        # change temperature so that the regulated temperature should slow down
        event_timestamp = now - timedelta(minutes=5)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await send_temperature_change_event(entity, 26, event_timestamp)
            await send_ext_temperature_change_event(entity, 35, event_timestamp)

            # the regulated temperature should be under
            assert entity.regulated_target_temp < entity.target_temperature
            assert (
                entity.regulated_target_temp == 25 - 1
            )  # +2.3 without round_to_nearest

            # change temperature so that the regulated temperature should slow down
        event_timestamp = now - timedelta(minutes=3)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await send_temperature_change_event(entity, 18, event_timestamp)
            await send_ext_temperature_change_event(entity, 25, event_timestamp)

            # the regulated temperature should be greater
            assert entity.regulated_target_temp > entity.target_temperature
            assert (
                entity.regulated_target_temp == 25 + 3
            )  # +0.4 without round_to_nearest


@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_regulation_limitations(
    hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
):
    """Test the limitations of the regulation of an over climate thermostat:
    1. test the period_min parameter: do not send regulation event too frequently
    2. test the dtemp parameter: do not send regulation event if offset temp is lower than dtemp
    """

    entry = MockConfigEntry(
        domain=DOMAIN,
        title="TheOverClimateMockName",
        unique_id="uniqueId",
        # This is include a medium regulation, dtemp=0.5, period_min=2
        data=PARTIAL_CLIMATE_CONFIG,
    )

    tz = get_tz(hass)  # pylint: disable=invalid-name
    now: datetime = datetime.now(tz=tz)

    fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})

    # Creates the regulated VTherm over climate at t-20
    # change temperature so that the heating will start
    event_timestamp = now - timedelta(minutes=20)

    with patch(
        "custom_components.versatile_thermostat.const.NowClass.get_now",
        return_value=event_timestamp,
    ), patch(
        "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
        return_value=fake_underlying_climate,
    ):
        entry.add_to_hass(hass)
        await hass.config_entries.async_setup(entry.entry_id)
        assert entry.state is ConfigEntryState.LOADED

        def find_my_entity(entity_id) -> ClimateEntity:
            """Find my new entity"""
            component: EntityComponent[ClimateEntity] = hass.data[CLIMATE_DOMAIN]
            for entity in component.entities:
                if entity.entity_id == entity_id:
                    return entity

        entity: ThermostatOverClimate = find_my_entity("climate.theoverclimatemockname")

        assert entity
        assert isinstance(entity, ThermostatOverClimate)

        assert entity.name == "TheOverClimateMockName"
        assert entity.is_over_climate is True
        assert entity.is_regulated is True

        entity._set_now(event_timestamp)
        # Will initialize the _last_regulation_change
        # Activate the heating by changing HVACMode and temperature
        # Select a hvacmode, presence and preset
        await entity.async_set_hvac_mode(HVACMode.HEAT)
        assert entity.hvac_mode is HVACMode.HEAT
        await entity.async_set_temperature(temperature=17)

        # it is cold today
        await send_temperature_change_event(entity, 15, event_timestamp)
        await send_ext_temperature_change_event(entity, 10, event_timestamp)

        # 1. set manual target temp (at now - 19) -> the regulation should be ignored because too early
        event_timestamp = now - timedelta(minutes=19)
        entity._set_now(event_timestamp)
        await entity.async_set_temperature(temperature=18)

        fake_underlying_climate.set_hvac_action(
            HVACAction.HEATING
        )  # simulate under heating
        assert entity.hvac_action == HVACAction.HEATING

        # the regulated temperature will not change because when we set temp manually it is forced
        assert entity.regulated_target_temp == 19.5

        # 2. set manual target temp (at now - 18) -> the regulation should be taken into account
        event_timestamp = now - timedelta(minutes=18)
        entity._set_now(event_timestamp)

        await entity.async_set_temperature(temperature=17)
        assert entity.regulated_target_temp > entity.target_temperature
        assert (
            entity.regulated_target_temp == 18 + 0
        )  # In strong we could go up to +3 degre. 0.7 without round_to_nearest
        old_regulated_temp = entity.regulated_target_temp

        # 3. change temperature so that dtemp < 0.5 and time is > period_min (+ 3min)
        event_timestamp = now - timedelta(minutes=15)
        entity._set_now(event_timestamp)
        await send_temperature_change_event(entity, 16, event_timestamp)
        await send_ext_temperature_change_event(entity, 10, event_timestamp)

        # the regulated temperature should be under
        assert entity.regulated_target_temp <= old_regulated_temp

        # 4. change temperature so that dtemp > 0.5 and time is > period_min (+ 3min)
        event_timestamp = now - timedelta(minutes=12)
        entity._set_now(event_timestamp)
        await send_ext_temperature_change_event(entity, 12, event_timestamp)
        await send_temperature_change_event(entity, 15, event_timestamp)

        # the regulated should have been done
        assert entity.regulated_target_temp != old_regulated_temp
        assert entity.regulated_target_temp >= entity.target_temperature
        assert entity.regulated_target_temp == 17 + 1.5  # 0.7 without round_to_nearest


@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
# Disable this test which is not working when run in // of others.
# I couldn't find out why
@pytest.mark.skip
async def test_over_climate_regulation_use_device_temp(
    hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
):
    """Test the regulation of an over climate thermostat"""

    entry = MockConfigEntry(
        domain=DOMAIN,
        title="TheOverClimateMockName",
        unique_id="uniqueId",
        # This is include a medium regulation
        data=PARTIAL_CLIMATE_CONFIG_USE_DEVICE_TEMP | {CONF_AUTO_REGULATION_DTEMP: 0.5},
    )

    tz = get_tz(hass)  # pylint: disable=invalid-name
    now: datetime = datetime.now(tz=tz)

    fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})

    assert fake_underlying_climate.current_temperature == 15

    # Creates the regulated VTherm over climate
    # change temperature so that the heating will start
    event_timestamp = now - timedelta(minutes=10)

    with patch(
        "custom_components.versatile_thermostat.const.NowClass.get_now",
        return_value=event_timestamp,
    ), patch(
        "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
        return_value=fake_underlying_climate,
    ):
        entity: ThermostatOverClimate = await create_thermostat(
            hass, entry, "climate.theoverclimatemockname"
        )
        assert entity
        assert isinstance(entity, ThermostatOverClimate)

        assert entity.name == "TheOverClimateMockName"
        assert entity.is_over_climate is True
        assert entity.is_regulated is True
        assert entity.auto_regulation_use_device_temp is True

        # 1.  Activate the heating by changing HVACMode and temperature
        # Select a hvacmode, presence and preset
        await entity.async_set_hvac_mode(HVACMode.HEAT)
        assert entity.hvac_mode is HVACMode.HEAT
        assert entity.regulated_target_temp == entity.min_temp

        await send_temperature_change_event(entity, 18, event_timestamp)
        await send_ext_temperature_change_event(entity, 10, event_timestamp)

        # 2. set manual target temp (at now - 7) -> no regulation should occurs
        # room temp is 18
        # target is 16
        # internal heater temp is 15
        fake_underlying_climate.set_current_temperature(15)
        event_timestamp = now - timedelta(minutes=7)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
            await entity.async_set_temperature(temperature=16)

            fake_underlying_climate.set_hvac_action(
                HVACAction.HEATING
            )  # simulate under heating
            assert entity.hvac_action == HVACAction.HEATING
            assert entity.preset_mode == PRESET_NONE  # Manual mode

            # the regulated temperature should be higher
            assert entity.regulated_target_temp < entity.target_temperature
            # The calcul is the following: 16 + (16 - 18) x 0.4 (strong) + 0 x ki - 1 (device offset)
            assert (
                entity.regulated_target_temp == 15
            )  # round(16 + (16 - 18) * 0.4 + 0 * 0.08)
            assert entity.hvac_action == HVACAction.HEATING

            mock_service_call.assert_has_calls(
                [
                    call.service_call(
                        "climate",
                        "set_temperature",
                        {
                            "entity_id": "climate.mock_climate",
                            # because device offset is -3 but not used because target is reach
                            "temperature": 15.0,
                            "target_temp_high": 30,
                            "target_temp_low": 15,
                        },
                    ),
                ]
            )

        # 3. change temperature so that the regulated temperature should slow down
        # HVACMODE.HEAT
        # room temp is 15
        # target is 18
        # internal heater temp is 20
        fake_underlying_climate.set_current_temperature(20.1)
        await entity.async_set_temperature(temperature=18)
        await send_ext_temperature_change_event(entity, 9, event_timestamp)

        event_timestamp = now - timedelta(minutes=5)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
            await send_temperature_change_event(entity, 15, event_timestamp)

            # the regulated temperature should be under (device offset is -2)
            assert entity.regulated_target_temp > entity.target_temperature
            assert entity.regulated_target_temp == 19.5  # round(18 + 1.4, 0.5)

            mock_service_call.assert_has_calls(
                [
                    call.service_call(
                        "climate",
                        "set_temperature",
                        {
                            "entity_id": "climate.mock_climate",
                            "temperature": 24.5,  # round(19.5 + 5, 0.5)
                            "target_temp_high": 30,
                            "target_temp_low": 15,
                        },
                    ),
                ]
            )

        # 4. In cool mode
        # room temp is 25
        # target is 23
        # internal heater temp is 27
        await entity.async_set_hvac_mode(HVACMode.COOL)
        await entity.async_set_temperature(temperature=23)
        fake_underlying_climate.set_current_temperature(26.9)
        await send_ext_temperature_change_event(entity, 30, event_timestamp)

        event_timestamp = now - timedelta(minutes=3)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ), patch("homeassistant.core.ServiceRegistry.async_call") as mock_service_call:
            await send_temperature_change_event(entity, 25, event_timestamp)

            # the regulated temperature should be upper (device offset is +1.9)
            assert entity.regulated_target_temp < entity.target_temperature
            assert entity.regulated_target_temp == 22.5

            mock_service_call.assert_has_calls(
                [
                    call.service_call(
                        "climate",
                        "set_temperature",
                        {
                            "entity_id": "climate.mock_climate",
                            "temperature": 24.5,  # round(22.5 + 1.9° of offset)
                            "target_temp_high": 30,
                            "target_temp_low": 15,
                        },
                    ),
                ]
            )

@pytest.mark.parametrize("expected_lingering_tasks", [True])
@pytest.mark.parametrize("expected_lingering_timers", [True])
async def test_over_climate_regulation_dtemp_null(
    hass: HomeAssistant, skip_hass_states_is_state, skip_send_event
):
    """Test the regulation of an over climate thermostat with no Dtemp limitation"""

    entry = MockConfigEntry(
        domain=DOMAIN,
        title="TheOverClimateMockName",
        unique_id="uniqueId",
        # This is include a medium regulation
        data=PARTIAL_CLIMATE_AC_CONFIG | {CONF_AUTO_REGULATION_DTEMP: 0, CONF_STEP_TEMPERATURE: 0.1},
    )

    tz = get_tz(hass)  # pylint: disable=invalid-name
    now: datetime = datetime.now(tz=tz)
    fake_underlying_climate = MockClimate(hass, "mockUniqueId", "MockClimateName", {})

    # Creates the regulated VTherm over climate
    # change temperature so that the heating will start
    event_timestamp = now - timedelta(minutes=20)

    with patch(
        "custom_components.versatile_thermostat.const.NowClass.get_now",
        return_value=event_timestamp,
    ), patch(
        "custom_components.versatile_thermostat.underlyings.UnderlyingClimate.find_underlying_climate",
        return_value=fake_underlying_climate,
    ):
        entity = await create_thermostat(hass, entry, "climate.theoverclimatemockname")

        assert entity
        assert isinstance(entity, ThermostatOverClimate)

        assert entity.name == "TheOverClimateMockName"
        assert entity.is_over_climate is True
        assert entity.is_regulated is True

        # Activate the heating by changing HVACMode and temperature
        # Select a hvacmode, presence and preset
        await entity.async_set_hvac_mode(HVACMode.HEAT)
        assert entity.hvac_mode is HVACMode.HEAT
        assert entity.hvac_action == HVACAction.OFF

        # change temperature so that the heating will start
        await send_temperature_change_event(entity, 15, event_timestamp)
        await send_ext_temperature_change_event(entity, 10, event_timestamp)

        # set manual target temp
        event_timestamp = now - timedelta(minutes=17)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await entity.async_set_temperature(temperature=20)

            fake_underlying_climate.set_hvac_action(
                HVACAction.HEATING
            )  # simulate under cooling
            assert entity.hvac_action == HVACAction.HEATING
            assert entity.preset_mode == PRESET_NONE  # Manual mode

            # the regulated temperature should be lower
            assert entity.regulated_target_temp > entity.target_temperature
            assert (
                entity.regulated_target_temp == 20 + 2.4
            )  # In medium we could go up to +3 degre
            assert entity.hvac_action == HVACAction.HEATING

        # change temperature so that the regulated temperature should slow down
        event_timestamp = now - timedelta(minutes=15)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await send_temperature_change_event(entity, 19, event_timestamp)
            await send_ext_temperature_change_event(entity, 10, event_timestamp)

            # the regulated temperature should be greater
            assert entity.regulated_target_temp > entity.target_temperature
            assert entity.regulated_target_temp == 20 + 0.9

            # change temperature so that the regulated temperature should slow down
        event_timestamp = now - timedelta(minutes=13)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await send_temperature_change_event(entity, 20, event_timestamp)
            await send_ext_temperature_change_event(entity, 10, event_timestamp)

            # the regulated temperature should be greater
            assert entity.regulated_target_temp > entity.target_temperature
            assert entity.regulated_target_temp == 20 + 0.5

        old_regulated_temp = entity.regulated_target_temp
        # Test if a small temperature change is taken into account : change temperature so that dtemp < 0.5 and time is > period_min (+ 3min)
        event_timestamp = now - timedelta(minutes=10)
        with patch(
            "custom_components.versatile_thermostat.const.NowClass.get_now",
            return_value=event_timestamp,
        ):
            await send_temperature_change_event(entity, 19.6, event_timestamp)
            await send_ext_temperature_change_event(entity, 10, event_timestamp)

            # the regulated temperature should be greater. This does not work if dtemp is not null
            assert entity.regulated_target_temp > old_regulated_temp
