"""Support for VELUX KLF 200 devices."""

from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
import logging
from typing import Any, override

from pyvlx import Node, PyVLXException

from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity

from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)


def velux_unique_id(node: Node, config_entry_id: str) -> str:
    """Build unique ID base for a Velux node."""
    return node.serial_number or f"{config_entry_id}_{node.node_id}"


def velux_device_info(
    hass: HomeAssistant, node: Node, config_entry_id: str
) -> DeviceInfo:
    """Build DeviceInfo for a Velux node."""
    unique_id = velux_unique_id(node, config_entry_id)
    return DeviceInfo(
        identifiers={(DOMAIN, unique_id)},
        name=node.name or f"#{node.node_id}",
        serial_number=node.serial_number,
        via_device_id=dr.async_get_device_id_by_identifier(
            hass,
            (DOMAIN, f"gateway_{config_entry_id}"),
            config_entry_id=config_entry_id,
        ),
    )


def wrap_pyvlx_call_exceptions[**P](
    func: Callable[P, Coroutine[Any, Any, None]],
) -> Callable[P, Coroutine[Any, Any, None]]:
    """Decorate pyvlx calls to handle exceptions.

    Catches OSError and PyVLXException and wraps them into HomeAssistantError
    with translation support.
    """

    @wraps(func)
    async def wrapper(*args: P.args, **kwargs: P.kwargs) -> None:
        """Wrap async function to catch exceptions thrown in pyvlx calls."""
        try:
            await func(*args, **kwargs)
        except (OSError, PyVLXException) as err:
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="device_communication_error",
                translation_placeholders={"error": str(err)},
            ) from err

    return wrapper


class VeluxEntity(Entity):
    """Abstraction for all Velux entities."""

    _attr_should_poll = False
    _attr_has_entity_name = True
    update_callback: Callable[[Node], Awaitable[None]] | None = None
    _attr_available = True
    _unavailable_logged = False

    def __init__(self, hass: HomeAssistant, node: Node, config_entry_id: str) -> None:
        """Initialize the Velux device."""
        self.node = node
        self._attr_unique_id = velux_unique_id(node, config_entry_id)
        self._attr_device_info = velux_device_info(hass, node, config_entry_id)

    async def after_update_callback(self, _: Node) -> None:
        """Call after device was updated."""
        self._attr_available = self.node.pyvlx.get_connected()
        if not self._attr_available:
            if not self._unavailable_logged:
                _LOGGER.info("Entity %s is unavailable", self.entity_id)
                self._unavailable_logged = True
        elif self._unavailable_logged:
            _LOGGER.info("Entity %s is back online", self.entity_id)
            self._unavailable_logged = False

        self.async_write_ha_state()

    @override
    async def async_added_to_hass(self) -> None:
        """Register callback and store reference for cleanup."""

        self.update_callback = self.after_update_callback
        self.node.register_device_updated_cb(self.update_callback)

    @override
    async def async_will_remove_from_hass(self) -> None:
        """Clean up registered callbacks."""
        if self.update_callback:
            self.node.unregister_device_updated_cb(self.update_callback)
            self.update_callback = None
