"""Utils for Comelit."""

from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
from typing import TYPE_CHECKING, Any, Concatenate

from aiocomelit.api import ComelitSerialBridgeObject
from aiocomelit.exceptions import CannotAuthenticate, CannotConnect, CannotRetrieveData
from aiohttp import ClientSession, CookieJar

from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import (
    aiohttp_client,
    device_registry as dr,
    entity_registry as er,
)

from .const import _LOGGER, DOMAIN, ObjectClassType
from .coordinator import ComelitBaseCoordinator
from .entity import ComelitBridgeBaseEntity


async def async_client_session(hass: HomeAssistant) -> ClientSession:
    """Return a new aiohttp session."""
    return aiohttp_client.async_create_clientsession(
        hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True)
    )


def load_api_data(device: ComelitSerialBridgeObject, domain: str) -> list[Any]:
    """Load data from the API."""
    # This function is called when the data is loaded from the API
    if not isinstance(device.val, list):
        raise HomeAssistantError(
            translation_domain=domain, translation_key="invalid_clima_data"
        )
    # CLIMATE has a 2 item tuple:
    # - first  for Clima
    # - second for Humidifier
    return device.val[0] if domain == CLIMATE_DOMAIN else device.val[1]


async def cleanup_stale_entity(
    hass: HomeAssistant,
    config_entry: ConfigEntry,
    entry_unique_id: str,
    device: ComelitSerialBridgeObject,
) -> None:
    """Cleanup stale entity."""
    entity_reg: er.EntityRegistry = er.async_get(hass)

    identifiers: list[str] = []

    for entry in er.async_entries_for_config_entry(entity_reg, config_entry.entry_id):
        if entry.unique_id == entry_unique_id:
            entry_name = entry.name or entry.original_name
            _LOGGER.info("Removing entity: %s [%s]", entry.entity_id, entry_name)
            entity_reg.async_remove(entry.entity_id)
            identifiers.append(f"{config_entry.entry_id}-{device.type}-{device.index}")

    if len(identifiers) > 0:
        _async_remove_state_config_entry_from_devices(hass, identifiers, config_entry)


def _async_remove_state_config_entry_from_devices(
    hass: HomeAssistant, identifiers: list[str], config_entry: ConfigEntry
) -> None:
    """Remove config entry from device."""

    device_registry = dr.async_get(hass)
    for identifier in identifiers:
        device = device_registry.async_get_device(identifiers={(DOMAIN, identifier)})
        if device:
            _LOGGER.info(
                "Removing config entry %s from device %s",
                config_entry.title,
                device.name,
            )
            device_registry.async_update_device(
                device_id=device.id,
                remove_config_entry_id=config_entry.entry_id,
            )


def bridge_api_call[_T: ComelitBridgeBaseEntity, **_P](
    func: Callable[Concatenate[_T, _P], Awaitable[None]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
    """Catch Bridge API call exceptions."""

    @wraps(func)
    async def cmd_wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
        """Wrap all command methods."""
        try:
            await func(self, *args, **kwargs)
        except CannotConnect as err:
            self.coordinator.last_update_success = False
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="cannot_connect",
                translation_placeholders={"error": repr(err)},
            ) from err
        except CannotRetrieveData as err:
            self.coordinator.last_update_success = False
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="cannot_retrieve_data",
                translation_placeholders={"error": repr(err)},
            ) from err
        except CannotAuthenticate:
            self.coordinator.last_update_success = False
            self.coordinator.config_entry.async_start_reauth(self.hass)

    return cmd_wrapper


def new_device_listener(
    coordinator: ComelitBaseCoordinator,
    new_devices_callback: Callable[
        [
            list[ObjectClassType],
            str,
        ],
        None,
    ],
    data_type: str,
) -> Callable[[], None]:
    """Subscribe to coordinator updates to check for new devices."""
    known_devices: dict[str, list[int]] = {}

    def _check_devices() -> None:
        """Check for new devices and call callback with any new monitors."""
        if TYPE_CHECKING:
            assert coordinator.data

        new_devices: list[ObjectClassType] = []
        for _id in coordinator.data[data_type]:
            if _id not in (id_list := known_devices.get(data_type, [])):
                known_devices.update({data_type: [*id_list, _id]})
                new_devices.append(coordinator.data[data_type][_id])

        if new_devices:
            new_devices_callback(new_devices, data_type)

    # Check for devices immediately
    _check_devices()

    return coordinator.async_add_listener(_check_devices)
