"""Support for VeSync fans."""

from __future__ import annotations

import logging
import math
from typing import Any

from pyvesync.vesyncbasedevice import VeSyncBaseDevice

from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.percentage import (
    percentage_to_ranged_value,
    ranged_value_to_percentage,
)
from homeassistant.util.scaling import int_states_in_range

from .common import is_fan
from .const import (
    DOMAIN,
    SKU_TO_BASE_DEVICE,
    VS_COORDINATOR,
    VS_DEVICES,
    VS_DISCOVERY,
    VS_FAN_MODE_ADVANCED_SLEEP,
    VS_FAN_MODE_AUTO,
    VS_FAN_MODE_MANUAL,
    VS_FAN_MODE_NORMAL,
    VS_FAN_MODE_PET,
    VS_FAN_MODE_PRESET_LIST_HA,
    VS_FAN_MODE_SLEEP,
    VS_FAN_MODE_TURBO,
)
from .coordinator import VeSyncDataCoordinator
from .entity import VeSyncBaseEntity

_LOGGER = logging.getLogger(__name__)

SPEED_RANGE = {  # off is not included
    "LV-PUR131S": (1, 3),
    "Core200S": (1, 3),
    "Core300S": (1, 3),
    "Core400S": (1, 4),
    "Core600S": (1, 4),
    "EverestAir": (1, 3),
    "Vital200S": (1, 4),
    "Vital100S": (1, 4),
    "SmartTowerFan": (1, 13),
}


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up the VeSync fan platform."""

    coordinator = hass.data[DOMAIN][VS_COORDINATOR]

    @callback
    def discover(devices):
        """Add new devices to platform."""
        _setup_entities(devices, async_add_entities, coordinator)

    config_entry.async_on_unload(
        async_dispatcher_connect(hass, VS_DISCOVERY.format(VS_DEVICES), discover)
    )

    _setup_entities(hass.data[DOMAIN][VS_DEVICES], async_add_entities, coordinator)


@callback
def _setup_entities(
    devices: list[VeSyncBaseDevice],
    async_add_entities,
    coordinator: VeSyncDataCoordinator,
):
    """Check if device is fan and add entity."""

    async_add_entities(VeSyncFanHA(dev, coordinator) for dev in devices if is_fan(dev))


class VeSyncFanHA(VeSyncBaseEntity, FanEntity):
    """Representation of a VeSync fan."""

    _attr_supported_features = (
        FanEntityFeature.SET_SPEED
        | FanEntityFeature.PRESET_MODE
        | FanEntityFeature.TURN_OFF
        | FanEntityFeature.TURN_ON
    )
    _attr_name = None
    _attr_translation_key = "vesync"

    @property
    def is_on(self) -> bool:
        """Return True if device is on."""
        return self.device.device_status == "on"

    @property
    def percentage(self) -> int | None:
        """Return the current speed."""
        if (
            self.device.mode == VS_FAN_MODE_MANUAL
            and (current_level := self.device.fan_level) is not None
        ):
            return ranged_value_to_percentage(
                SPEED_RANGE[SKU_TO_BASE_DEVICE[self.device.device_type]], current_level
            )
        return None

    @property
    def speed_count(self) -> int:
        """Return the number of speeds the fan supports."""
        return int_states_in_range(
            SPEED_RANGE[SKU_TO_BASE_DEVICE[self.device.device_type]]
        )

    @property
    def preset_modes(self) -> list[str]:
        """Get the list of available preset modes."""
        if hasattr(self.device, "modes"):
            return sorted(
                [
                    mode
                    for mode in self.device.modes
                    if mode in VS_FAN_MODE_PRESET_LIST_HA
                ]
            )
        return []

    @property
    def preset_mode(self) -> str | None:
        """Get the current preset mode."""
        if self.device.mode in VS_FAN_MODE_PRESET_LIST_HA:
            return self.device.mode
        return None

    @property
    def extra_state_attributes(self) -> dict[str, Any]:
        """Return the state attributes of the fan."""
        attr = {}

        if hasattr(self.device, "active_time"):
            attr["active_time"] = self.device.active_time

        if hasattr(self.device, "screen_status"):
            attr["screen_status"] = self.device.screen_status

        if hasattr(self.device, "child_lock"):
            attr["child_lock"] = self.device.child_lock

        if hasattr(self.device, "night_light"):
            attr["night_light"] = self.device.night_light

        if hasattr(self.device, "mode"):
            attr["mode"] = self.device.mode

        return attr

    def set_percentage(self, percentage: int) -> None:
        """Set the speed of the device.

        If percentage is 0, turn off the fan. Otherwise, ensure the fan is on,
        set manual mode if needed, and set the speed.
        """
        device_type = SKU_TO_BASE_DEVICE[self.device.device_type]
        speed_range = SPEED_RANGE[device_type]

        if percentage == 0:
            # Turning off is a special case: do not set speed or mode
            if not self.device.turn_off():
                raise HomeAssistantError("An error occurred while turning off.")
            self.schedule_update_ha_state()
            return

        # If the fan is off, turn it on first
        if not self.device.is_on:
            if not self.device.turn_on():
                raise HomeAssistantError("An error occurred while turning on.")

        # Switch to manual mode if not already set
        if self.device.mode != VS_FAN_MODE_MANUAL:
            if not self.device.manual_mode():
                raise HomeAssistantError("An error occurred while setting manual mode.")

        # Calculate the speed level and set it
        speed_level = math.ceil(percentage_to_ranged_value(speed_range, percentage))
        if not self.device.change_fan_speed(speed_level):
            raise HomeAssistantError("An error occurred while changing fan speed.")

        self.schedule_update_ha_state()

    def set_preset_mode(self, preset_mode: str) -> None:
        """Set the preset mode of device."""
        if preset_mode not in VS_FAN_MODE_PRESET_LIST_HA:
            raise ValueError(
                f"{preset_mode} is not one of the valid preset modes: "
                f"{VS_FAN_MODE_PRESET_LIST_HA}"
            )

        if not self.device.is_on:
            self.device.turn_on()

        if preset_mode == VS_FAN_MODE_AUTO:
            success = self.device.auto_mode()
        elif preset_mode == VS_FAN_MODE_SLEEP:
            success = self.device.sleep_mode()
        elif preset_mode == VS_FAN_MODE_ADVANCED_SLEEP:
            success = self.device.advanced_sleep_mode()
        elif preset_mode == VS_FAN_MODE_PET:
            success = self.device.pet_mode()
        elif preset_mode == VS_FAN_MODE_TURBO:
            success = self.device.turbo_mode()
        elif preset_mode == VS_FAN_MODE_NORMAL:
            success = self.device.normal_mode()
        if not success:
            raise HomeAssistantError("An error occurred while setting preset mode.")

        self.schedule_update_ha_state()

    def turn_on(
        self,
        percentage: int | None = None,
        preset_mode: str | None = None,
        **kwargs: Any,
    ) -> None:
        """Turn the device on."""
        if preset_mode:
            self.set_preset_mode(preset_mode)
            return
        if percentage is None:
            percentage = 50
        self.set_percentage(percentage)

    def turn_off(self, **kwargs: Any) -> None:
        """Turn the device off."""
        success = self.device.turn_off()
        if not success:
            raise HomeAssistantError("An error occurred while turning off.")
        self.schedule_update_ha_state()
