"""Tests for fan platforms."""

from unittest.mock import patch

import pytest

from homeassistant.components import fan
from homeassistant.components.fan import (
    ATTR_PRESET_MODE,
    ATTR_PRESET_MODES,
    DOMAIN,
    SERVICE_SET_PRESET_MODE,
    FanEntity,
    FanEntityFeature,
    NotValidPresetModeError,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
import homeassistant.helpers.entity_registry as er
from homeassistant.setup import async_setup_component

from .common import MockFan

from tests.common import (
    MockConfigEntry,
    MockModule,
    MockPlatform,
    help_test_all,
    import_and_test_deprecated_constant_enum,
    mock_integration,
    mock_platform,
    setup_test_component_platform,
)


class BaseFan(FanEntity):
    """Implementation of the abstract FanEntity."""

    def __init__(self) -> None:
        """Initialize the fan."""


def test_fanentity() -> None:
    """Test fan entity methods."""
    fan = BaseFan()
    assert fan.state == "off"
    assert fan.preset_modes is None
    assert fan.supported_features == 0
    assert fan.percentage_step == 1
    assert fan.speed_count == 100
    assert fan.capability_attributes == {}
    # Test set_speed not required
    with pytest.raises(NotImplementedError):
        fan.oscillate(True)
    with pytest.raises(AttributeError):
        fan.set_speed("low")
    with pytest.raises(NotImplementedError):
        fan.set_percentage(0)
    with pytest.raises(NotImplementedError):
        fan.set_preset_mode("auto")
    with pytest.raises(NotImplementedError):
        fan.turn_on()
    with pytest.raises(NotImplementedError):
        fan.turn_off()


async def test_async_fanentity(hass: HomeAssistant) -> None:
    """Test async fan entity methods."""
    fan = BaseFan()
    fan.hass = hass
    assert fan.state == "off"
    assert fan.preset_modes is None
    assert fan.supported_features == 0
    assert fan.percentage_step == 1
    assert fan.speed_count == 100
    assert fan.capability_attributes == {}
    # Test set_speed not required
    with pytest.raises(NotImplementedError):
        await fan.async_oscillate(True)
    with pytest.raises(AttributeError):
        await fan.async_set_speed("low")
    with pytest.raises(NotImplementedError):
        await fan.async_set_percentage(0)
    with pytest.raises(NotImplementedError):
        await fan.async_set_preset_mode("auto")
    with pytest.raises(NotImplementedError):
        await fan.async_turn_on()
    with pytest.raises(NotImplementedError):
        await fan.async_turn_off()
    with pytest.raises(NotImplementedError):
        await fan.async_increase_speed()
    with pytest.raises(NotImplementedError):
        await fan.async_decrease_speed()


@pytest.mark.parametrize(
    ("attribute_name", "attribute_value"),
    [
        ("current_direction", "forward"),
        ("oscillating", True),
        ("percentage", 50),
        ("preset_mode", "medium"),
        ("preset_modes", ["low", "medium", "high"]),
        ("speed_count", 50),
        ("supported_features", 1),
    ],
)
def test_fanentity_attributes(attribute_name, attribute_value) -> None:
    """Test fan entity attribute shorthand."""
    fan = BaseFan()
    setattr(fan, f"_attr_{attribute_name}", attribute_value)
    assert getattr(fan, attribute_name) == attribute_value


async def test_preset_mode_validation(
    hass: HomeAssistant,
    caplog: pytest.LogCaptureFixture,
    entity_registry: er.EntityRegistry,
) -> None:
    """Test preset mode validation."""
    await hass.async_block_till_done()

    test_fan = MockFan(
        name="Support fan with preset_mode support",
        supported_features=FanEntityFeature.PRESET_MODE,
        unique_id="unique_support_preset_mode",
        preset_modes=["auto", "eco"],
    )
    setup_test_component_platform(hass, "fan", [test_fan])

    assert await async_setup_component(hass, "fan", {"fan": {"platform": "test"}})
    await hass.async_block_till_done()

    state = hass.states.get("fan.support_fan_with_preset_mode_support")
    assert state.attributes.get(ATTR_PRESET_MODES) == ["auto", "eco"]

    await hass.services.async_call(
        DOMAIN,
        SERVICE_SET_PRESET_MODE,
        {
            "entity_id": "fan.support_fan_with_preset_mode_support",
            "preset_mode": "eco",
        },
        blocking=True,
    )

    state = hass.states.get("fan.support_fan_with_preset_mode_support")
    assert state.attributes.get(ATTR_PRESET_MODE) == "eco"

    with pytest.raises(NotValidPresetModeError) as exc:
        await hass.services.async_call(
            DOMAIN,
            SERVICE_SET_PRESET_MODE,
            {
                "entity_id": "fan.support_fan_with_preset_mode_support",
                "preset_mode": "invalid",
            },
            blocking=True,
        )
    assert exc.value.translation_key == "not_valid_preset_mode"

    with pytest.raises(NotValidPresetModeError) as exc:
        await test_fan._valid_preset_mode_or_raise("invalid")
    assert exc.value.translation_key == "not_valid_preset_mode"


def test_all() -> None:
    """Test module.__all__ is correctly set."""
    help_test_all(fan)


@pytest.mark.parametrize(("enum"), list(fan.FanEntityFeature))
def test_deprecated_constants(
    caplog: pytest.LogCaptureFixture,
    enum: fan.FanEntityFeature,
) -> None:
    """Test deprecated constants."""
    if not FanEntityFeature.TURN_OFF and not FanEntityFeature.TURN_ON:
        import_and_test_deprecated_constant_enum(
            caplog, fan, enum, "SUPPORT_", "2025.1"
        )


def test_deprecated_supported_features_ints(caplog: pytest.LogCaptureFixture) -> None:
    """Test deprecated supported features ints."""

    class MockFan(FanEntity):
        @property
        def supported_features(self) -> int:
            """Return supported features."""
            return 1

    entity = MockFan()
    assert entity.supported_features is FanEntityFeature(1)
    assert "MockFan" in caplog.text
    assert "is using deprecated supported features values" in caplog.text
    assert "Instead it should use" in caplog.text
    assert "FanEntityFeature.SET_SPEED" in caplog.text
    caplog.clear()
    assert entity.supported_features is FanEntityFeature(1)
    assert "is using deprecated supported features values" not in caplog.text


async def test_warning_not_implemented_turn_on_off_feature(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
    """Test adding feature flag and warn if missing when methods are set."""

    called = []

    class MockFanEntityTest(MockFan):
        """Mock Fan device."""

        def turn_on(
            self,
            percentage: int | None = None,
            preset_mode: str | None = None,
        ) -> None:
            """Turn on."""
            called.append("turn_on")

        def turn_off(self) -> None:
            """Turn off."""
            called.append("turn_off")

    async def async_setup_entry_init(
        hass: HomeAssistant, config_entry: ConfigEntry
    ) -> bool:
        """Set up test config entry."""
        await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
        return True

    async def async_setup_entry_fan_platform(
        hass: HomeAssistant,
        config_entry: ConfigEntry,
        async_add_entities: AddEntitiesCallback,
    ) -> None:
        """Set up test fan platform via config entry."""
        async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])

    mock_integration(
        hass,
        MockModule(
            "test",
            async_setup_entry=async_setup_entry_init,
        ),
        built_in=False,
    )
    mock_platform(
        hass,
        "test.fan",
        MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
    )

    with patch.object(
        MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
    ):
        config_entry = MockConfigEntry(domain="test")
        config_entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

    state = hass.states.get("fan.test")
    assert state is not None

    assert (
        "Entity fan.test (<class 'tests.custom_components.fan.test_init.test_warning_not_implemented_turn_on_off_feature.<locals>.MockFanEntityTest'>) "
        "does not set FanEntityFeature.TURN_OFF but implements the turn_off method. Please report it to the author of the 'test' custom integration"
        in caplog.text
    )
    assert (
        "Entity fan.test (<class 'tests.custom_components.fan.test_init.test_warning_not_implemented_turn_on_off_feature.<locals>.MockFanEntityTest'>) "
        "does not set FanEntityFeature.TURN_ON but implements the turn_on method. Please report it to the author of the 'test' custom integration"
        in caplog.text
    )

    await hass.services.async_call(
        DOMAIN,
        SERVICE_TURN_ON,
        {
            "entity_id": "fan.test",
        },
        blocking=True,
    )
    await hass.services.async_call(
        DOMAIN,
        SERVICE_TURN_OFF,
        {
            "entity_id": "fan.test",
        },
        blocking=True,
    )

    assert len(called) == 2
    assert "turn_on" in called
    assert "turn_off" in called


async def test_no_warning_implemented_turn_on_off_feature(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
    """Test no warning when feature flags are set."""

    class MockFanEntityTest(MockFan):
        """Mock Fan device."""

        _attr_supported_features = (
            FanEntityFeature.DIRECTION
            | FanEntityFeature.OSCILLATE
            | FanEntityFeature.SET_SPEED
            | FanEntityFeature.PRESET_MODE
            | FanEntityFeature.TURN_OFF
            | FanEntityFeature.TURN_ON
        )

    async def async_setup_entry_init(
        hass: HomeAssistant, config_entry: ConfigEntry
    ) -> bool:
        """Set up test config entry."""
        await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
        return True

    async def async_setup_entry_fan_platform(
        hass: HomeAssistant,
        config_entry: ConfigEntry,
        async_add_entities: AddEntitiesCallback,
    ) -> None:
        """Set up test fan platform via config entry."""
        async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])

    mock_integration(
        hass,
        MockModule(
            "test",
            async_setup_entry=async_setup_entry_init,
        ),
        built_in=False,
    )
    mock_platform(
        hass,
        "test.fan",
        MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
    )

    with patch.object(
        MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
    ):
        config_entry = MockConfigEntry(domain="test")
        config_entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

    state = hass.states.get("fan.test")
    assert state is not None

    assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
    assert "does not set FanEntityFeature.TURN_ON" not in caplog.text


async def test_no_warning_integration_has_migrated(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
    """Test no warning when integration migrated using `_enable_turn_on_off_backwards_compatibility`."""

    class MockFanEntityTest(MockFan):
        """Mock Fan device."""

        _enable_turn_on_off_backwards_compatibility = False
        _attr_supported_features = (
            FanEntityFeature.DIRECTION
            | FanEntityFeature.OSCILLATE
            | FanEntityFeature.SET_SPEED
            | FanEntityFeature.PRESET_MODE
        )

    async def async_setup_entry_init(
        hass: HomeAssistant, config_entry: ConfigEntry
    ) -> bool:
        """Set up test config entry."""
        await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
        return True

    async def async_setup_entry_fan_platform(
        hass: HomeAssistant,
        config_entry: ConfigEntry,
        async_add_entities: AddEntitiesCallback,
    ) -> None:
        """Set up test fan platform via config entry."""
        async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])

    mock_integration(
        hass,
        MockModule(
            "test",
            async_setup_entry=async_setup_entry_init,
        ),
        built_in=False,
    )
    mock_platform(
        hass,
        "test.fan",
        MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
    )

    with patch.object(
        MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
    ):
        config_entry = MockConfigEntry(domain="test")
        config_entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

    state = hass.states.get("fan.test")
    assert state is not None

    assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
    assert "does not set FanEntityFeature.TURN_ON" not in caplog.text


async def test_no_warning_integration_implement_feature_flags(
    hass: HomeAssistant, caplog: pytest.LogCaptureFixture, config_flow_fixture: None
) -> None:
    """Test no warning when integration uses the correct feature flags."""

    class MockFanEntityTest(MockFan):
        """Mock Fan device."""

        _attr_supported_features = (
            FanEntityFeature.DIRECTION
            | FanEntityFeature.OSCILLATE
            | FanEntityFeature.SET_SPEED
            | FanEntityFeature.PRESET_MODE
            | FanEntityFeature.TURN_OFF
            | FanEntityFeature.TURN_ON
        )

    async def async_setup_entry_init(
        hass: HomeAssistant, config_entry: ConfigEntry
    ) -> bool:
        """Set up test config entry."""
        await hass.config_entries.async_forward_entry_setups(config_entry, [DOMAIN])
        return True

    async def async_setup_entry_fan_platform(
        hass: HomeAssistant,
        config_entry: ConfigEntry,
        async_add_entities: AddEntitiesCallback,
    ) -> None:
        """Set up test fan platform via config entry."""
        async_add_entities([MockFanEntityTest(name="test", entity_id="fan.test")])

    mock_integration(
        hass,
        MockModule(
            "test",
            async_setup_entry=async_setup_entry_init,
        ),
        built_in=False,
    )
    mock_platform(
        hass,
        "test.fan",
        MockPlatform(async_setup_entry=async_setup_entry_fan_platform),
    )

    with patch.object(
        MockFanEntityTest, "__module__", "tests.custom_components.fan.test_init"
    ):
        config_entry = MockConfigEntry(domain="test")
        config_entry.add_to_hass(hass)
        assert await hass.config_entries.async_setup(config_entry.entry_id)
        await hass.async_block_till_done()

    state = hass.states.get("fan.test")
    assert state is not None

    assert "does not set FanEntityFeature.TURN_OFF" not in caplog.text
    assert "does not set FanEntityFeature.TURN_ON" not in caplog.text