"""The homee cover platform."""

import logging
from typing import Any, cast

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

OPEN_CLOSE_ATTRIBUTES = [
    AttributeType.OPEN_CLOSE,
    AttributeType.SLAT_ROTATION_IMPULSE,
    AttributeType.UP_DOWN,
]
POSITION_ATTRIBUTES = [AttributeType.POSITION, AttributeType.SHUTTER_SLAT_POSITION]


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 based on the available attributes."""
    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

    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."""
    COVER_DEVICE_PROFILES = {
        NodeProfile.GARAGE_DOOR_OPERATOR: CoverDeviceClass.GARAGE,
        NodeProfile.ENTRANCE_GATE_OPERATOR: CoverDeviceClass.GATE,
        NodeProfile.SHUTTER_POSITION_SWITCH: CoverDeviceClass.SHUTTER,
    }

    return COVER_DEVICE_PROFILES.get(node.profile)


async def add_cover_entities(
    config_entry: HomeeConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
    nodes: list[HomeeNode],
) -> None:
    """Add homee cover entities."""
    async_add_entities(
        HomeeCover(node, config_entry) for node in nodes if is_cover_node(node)
    )


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(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 based on its profile and attributes."""
    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, node: HomeeNode, entry: HomeeConfigEntry) -> None:
        """Initialize a homee cover entity."""
        super().__init__(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
    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
    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
    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() == 3
                if not self._open_close_attribute.is_reversed
                else self._open_close_attribute.get_value() == 4
            )

        return None

    @property
    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() == 4
                if not self._open_close_attribute.is_reversed
                else self._open_close_attribute.get_value() == 3
            )

        return None

    @property
    def is_closed(self) -> bool | None:
        """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() == 1

            return self._open_close_attribute.get_value() == 0

        # If none of the above is present, it might be a slat only cover.
        if (
            attribute := self._node.get_attribute_by_type(
                AttributeType.SHUTTER_SLAT_POSITION
            )
        ) is not None:
            return attribute.get_value() == attribute.minimum

        return None

    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, 0)
        else:
            await self.async_set_homee_value(self._open_close_attribute, 1)

    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, 1)
        else:
            await self.async_set_homee_value(self._open_close_attribute, 0)

    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)

    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, 2)

    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, 2)
            else:
                await self.async_set_homee_value(slat_attribute, 1)

    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, 1)
            else:
                await self.async_set_homee_value(slat_attribute, 2)

    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)
