"""The homee cover platform."""

from enum import IntEnum
import logging
from typing import TYPE_CHECKING, Any, cast, override

from pyHomee.const import AttributeType, NodeProfile
from pyHomee.model import HomeeAttribute, HomeeNode

from homeassistant.components.cover import (
    ATTR_POSITION,
    ATTR_TILT_POSITION,
    CoverDeviceClass,
    CoverEntity,
    CoverEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import HomeeConfigEntry
from .entity import HomeeNodeEntity
from .helpers import setup_homee_platform

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 0

COVER_DEVICE_PROFILES = {
    NodeProfile.GARAGE_DOOR_OPERATOR: CoverDeviceClass.GARAGE,
    NodeProfile.ENTRANCE_GATE_OPERATOR: CoverDeviceClass.GATE,
    NodeProfile.SHUTTER_POSITION_SWITCH: CoverDeviceClass.SHUTTER,
}
IS_CLOSED_ATTRIBUTES = [
    AttributeType.OPEN_CLOSE,
    AttributeType.UP_DOWN,
    AttributeType.POSITION,
    AttributeType.SHUTTER_SLAT_POSITION,
]


class HomeeCoverState(IntEnum):
    """Open/closed states for covers in homee."""

    OPEN = 0
    CLOSED = 1
    STOPPED = 2
    OPENING = 3
    CLOSING = 4


class HomeeSlatState(IntEnum):
    """Slat states for covers in homee."""

    STOPPED = 0
    CLOSED = 1
    OPEN = 2


def get_open_close_attribute(node: HomeeNode) -> HomeeAttribute | None:
    """Return the attribute used for opening/closing the cover."""
    # We assume, that no device has UP_DOWN and OPEN_CLOSE, but only one of them.
    if (open_close := node.get_attribute_by_type(AttributeType.UP_DOWN)) is None:
        open_close = node.get_attribute_by_type(AttributeType.OPEN_CLOSE)

    return open_close


def get_cover_features(
    node: HomeeNode, open_close_attribute: HomeeAttribute | None
) -> CoverEntityFeature:
    """Determine the supported cover features of a homee node."""
    features = CoverEntityFeature(0)

    if (open_close_attribute is not None) and open_close_attribute.editable:
        features |= (
            CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE | CoverEntityFeature.STOP
        )

    # Check for up/down position settable.
    attribute = node.get_attribute_by_type(AttributeType.POSITION)
    if attribute is not None:
        if attribute.editable:
            features |= CoverEntityFeature.SET_POSITION

    if node.get_attribute_by_type(AttributeType.SLAT_ROTATION_IMPULSE) is not None:
        features |= (
            CoverEntityFeature.OPEN_TILT
            | CoverEntityFeature.CLOSE_TILT
            | CoverEntityFeature.STOP_TILT
        )

    if node.get_attribute_by_type(AttributeType.SHUTTER_SLAT_POSITION) is not None:
        features |= CoverEntityFeature.SET_TILT_POSITION

    return features


def get_device_class(node: HomeeNode) -> CoverDeviceClass | None:
    """Determine the device class a homee node based on the node profile."""
    return COVER_DEVICE_PROFILES.get(node.profile)


async def add_cover_entities(
    hass: HomeAssistant,
    config_entry: HomeeConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
    nodes: list[HomeeNode],
) -> None:
    """Add homee cover entities."""
    entities: list[HomeeNode] = []
    for node in nodes:
        if is_cover_node(node):
            if any(
                node.get_attribute_by_type(attr) is not None
                for attr in IS_CLOSED_ATTRIBUTES
            ):
                entities.append(node)
            else:
                _LOGGER.warning(
                    "Cover %s could not be added, because it is missing an Attribute "
                    "for closed indication. Please open an issue at "
                    "https://github.com/home-assistant/core/issues",
                    node.name,
                )

    async_add_entities(HomeeCover(hass, cover, config_entry) for cover in entities)


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: HomeeConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Add the homee platform for the cover integration."""

    await setup_homee_platform(
        hass, add_cover_entities, async_add_entities, config_entry
    )


def is_cover_node(node: HomeeNode) -> bool:
    """Determine if a node is controllable as a homee cover."""
    return node.profile in [
        NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH,
        NodeProfile.ELECTRIC_MOTOR_METERING_SWITCH_WITHOUT_SLAT_POSITION,
        NodeProfile.ENTRANCE_GATE_OPERATOR,
        NodeProfile.GARAGE_DOOR_OPERATOR,
        NodeProfile.SHUTTER_POSITION_SWITCH,
    ]


class HomeeCover(HomeeNodeEntity, CoverEntity):
    """Representation of a homee cover device."""

    _attr_name = None

    def __init__(
        self, hass: HomeAssistant, node: HomeeNode, entry: HomeeConfigEntry
    ) -> None:
        """Initialize a homee cover entity."""
        super().__init__(hass, node, entry)
        self._open_close_attribute = get_open_close_attribute(node)
        self._attr_supported_features = get_cover_features(
            node, self._open_close_attribute
        )
        self._attr_device_class = get_device_class(node)
        self._attr_unique_id = (
            f"{self._attr_unique_id}-{self._open_close_attribute.id}"
            if self._open_close_attribute is not None
            else f"{self._attr_unique_id}-0"
        )

    @property
    @override
    def current_cover_position(self) -> int | None:
        """Return the cover's position."""
        # Translate the homee position values to HA's 0-100 scale
        if (
            attribute := self._node.get_attribute_by_type(AttributeType.POSITION)
        ) is not None:
            homee_min = attribute.minimum
            homee_max = attribute.maximum
            homee_position = attribute.current_value
            position = ((homee_position - homee_min) / (homee_max - homee_min)) * 100

            return int(100 - position)

        return None

    @property
    @override
    def current_cover_tilt_position(self) -> int | None:
        """Return the cover's tilt position."""
        # Translate the homee position values to HA's 0-100 scale
        if (
            attribute := self._node.get_attribute_by_type(
                AttributeType.SHUTTER_SLAT_POSITION
            )
        ) is not None:
            homee_min = attribute.minimum
            homee_max = attribute.maximum
            homee_position = attribute.current_value
            position = ((homee_position - homee_min) / (homee_max - homee_min)) * 100

            return int(100 - position)

        return None

    @property
    @override
    def is_opening(self) -> bool | None:
        """Return the opening status of the cover."""
        if self._open_close_attribute is not None:
            return (
                self._open_close_attribute.get_value() == HomeeCoverState.OPENING
                if not self._open_close_attribute.is_reversed
                else self._open_close_attribute.get_value() == HomeeCoverState.CLOSING
            )

        return None

    @property
    @override
    def is_closing(self) -> bool | None:
        """Return the closing status of the cover."""
        if self._open_close_attribute is not None:
            return (
                self._open_close_attribute.get_value() == HomeeCoverState.CLOSING
                if not self._open_close_attribute.is_reversed
                else self._open_close_attribute.get_value() == HomeeCoverState.OPENING
            )

        return None

    @property
    @override
    def is_closed(self) -> bool:
        """Return if the cover is closed."""
        if (
            attribute := self._node.get_attribute_by_type(AttributeType.POSITION)
        ) is not None:
            return attribute.get_value() == attribute.maximum

        if self._open_close_attribute is not None:
            if not self._open_close_attribute.is_reversed:
                return self._open_close_attribute.get_value() == HomeeCoverState.CLOSED

            return self._open_close_attribute.get_value() == HomeeCoverState.OPEN

        # If none of the above is present, it will be a slat only cover.
        attribute = self._node.get_attribute_by_type(
            AttributeType.SHUTTER_SLAT_POSITION
        )
        if TYPE_CHECKING:
            # This case should not happen, because we check for
            # the presence of an IS_CLOSED_ATTRIBUTE when adding entities.
            assert attribute is not None

        return attribute.get_value() == attribute.minimum

    @override
    async def async_open_cover(self, **kwargs: Any) -> None:
        """Open the cover."""
        assert self._open_close_attribute is not None
        if not self._open_close_attribute.is_reversed:
            await self.async_set_homee_value(
                self._open_close_attribute, HomeeCoverState.OPEN
            )
        else:
            await self.async_set_homee_value(
                self._open_close_attribute, HomeeCoverState.CLOSED
            )

    @override
    async def async_close_cover(self, **kwargs: Any) -> None:
        """Close cover."""
        assert self._open_close_attribute is not None
        if not self._open_close_attribute.is_reversed:
            await self.async_set_homee_value(
                self._open_close_attribute, HomeeCoverState.CLOSED
            )
        else:
            await self.async_set_homee_value(
                self._open_close_attribute, HomeeCoverState.OPEN
            )

    @override
    async def async_set_cover_position(self, **kwargs: Any) -> None:
        """Move the cover to a specific position."""
        if CoverEntityFeature.SET_POSITION in self.supported_features:
            position = 100 - cast(int, kwargs[ATTR_POSITION])

            # Convert position to range of our entity.
            if (
                attribute := self._node.get_attribute_by_type(AttributeType.POSITION)
            ) is not None:
                homee_min = attribute.minimum
                homee_max = attribute.maximum
                homee_position = (position / 100) * (homee_max - homee_min) + homee_min

                await self.async_set_homee_value(attribute, homee_position)

    @override
    async def async_stop_cover(self, **kwargs: Any) -> None:
        """Stop the cover."""
        if self._open_close_attribute is not None:
            await self.async_set_homee_value(
                self._open_close_attribute, HomeeCoverState.STOPPED
            )

    @override
    async def async_open_cover_tilt(self, **kwargs: Any) -> None:
        """Open the cover tilt."""
        if (
            slat_attribute := self._node.get_attribute_by_type(
                AttributeType.SLAT_ROTATION_IMPULSE
            )
        ) is not None:
            if not slat_attribute.is_reversed:
                await self.async_set_homee_value(slat_attribute, HomeeSlatState.OPEN)
            else:
                await self.async_set_homee_value(slat_attribute, HomeeSlatState.CLOSED)

    @override
    async def async_close_cover_tilt(self, **kwargs: Any) -> None:
        """Close the cover tilt."""
        if (
            slat_attribute := self._node.get_attribute_by_type(
                AttributeType.SLAT_ROTATION_IMPULSE
            )
        ) is not None:
            if not slat_attribute.is_reversed:
                await self.async_set_homee_value(slat_attribute, HomeeSlatState.CLOSED)
            else:
                await self.async_set_homee_value(slat_attribute, HomeeSlatState.OPEN)

    @override
    async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
        """Stop the cover tilt."""
        if (
            slat_attribute := self._node.get_attribute_by_type(
                AttributeType.SLAT_ROTATION_IMPULSE
            )
        ) is not None:
            await self.async_set_homee_value(slat_attribute, HomeeSlatState.STOPPED)

    @override
    async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
        """Move the cover tilt to a specific position."""
        if CoverEntityFeature.SET_TILT_POSITION in self.supported_features:
            position = 100 - cast(int, kwargs[ATTR_TILT_POSITION])

            # Convert position to range of our entity.
            if (
                attribute := self._node.get_attribute_by_type(
                    AttributeType.SHUTTER_SLAT_POSITION
                )
            ) is not None:
                homee_min = attribute.minimum
                homee_max = attribute.maximum
                homee_position = (position / 100) * (homee_max - homee_min) + homee_min

                await self.async_set_homee_value(attribute, homee_position)
