"""Module for SIA Base Entity."""
from __future__ import annotations

from abc import abstractmethod
import logging
from typing import Any

from pysiaalarm import SIAEvent

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PORT
from homeassistant.core import CALLBACK_TYPE, State, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.restore_state import RestoreEntity

from .const import CONF_ACCOUNT, CONF_PING_INTERVAL, DOMAIN, SIA_EVENT, SIA_NAME_FORMAT
from .utils import get_attr_from_sia_event, get_unavailability_interval

_LOGGER = logging.getLogger(__name__)


class SIABaseEntity(RestoreEntity):
    """Base class for SIA entities."""

    def __init__(
        self,
        entry: ConfigEntry,
        account_data: dict[str, Any],
        zone: int,
        device_class: str,
    ) -> None:
        """Create SIABaseEntity object."""
        self._entry: ConfigEntry = entry
        self._account_data: dict[str, Any] = account_data
        self._zone: int = zone
        self._attr_device_class: str = device_class

        self._port: int = self._entry.data[CONF_PORT]
        self._account: str = self._account_data[CONF_ACCOUNT]
        self._ping_interval: int = self._account_data[CONF_PING_INTERVAL]

        self._cancel_availability_cb: CALLBACK_TYPE | None = None

        self._attr_extra_state_attributes = {}
        self._attr_should_poll = False
        self._attr_name = SIA_NAME_FORMAT.format(
            self._port, self._account, self._zone, self._attr_device_class
        )

    async def async_added_to_hass(self) -> None:
        """Run when entity about to be added to hass.

        Overridden from Entity.

        1. register the dispatcher and add the callback to on_remove
        2. get previous state from storage and pass to entity specific function
        3. if available: create availability cb
        """
        self.async_on_remove(
            async_dispatcher_connect(
                self.hass,
                SIA_EVENT.format(self._port, self._account),
                self.async_handle_event,
            )
        )
        self.handle_last_state(await self.async_get_last_state())
        if self._attr_available:
            self.async_create_availability_cb()

    @abstractmethod
    def handle_last_state(self, last_state: State | None) -> None:
        """Handle the last state."""

    async def async_will_remove_from_hass(self) -> None:
        """Run when entity will be removed from hass.

        Overridden from Entity.
        """
        if self._cancel_availability_cb:
            self._cancel_availability_cb()

    @callback
    def async_handle_event(self, sia_event: SIAEvent) -> None:
        """Listen to dispatcher events for this port and account and update state and attributes.

        If the port and account combo receives any message it means it is online and can therefore be set to available.
        """
        _LOGGER.debug("Received event: %s", sia_event)
        if int(sia_event.ri) == self._zone:
            self._attr_extra_state_attributes.update(get_attr_from_sia_event(sia_event))
            self.update_state(sia_event)
        self.async_reset_availability_cb()
        self.async_write_ha_state()

    @abstractmethod
    def update_state(self, sia_event: SIAEvent) -> None:
        """Do the entity specific state updates."""

    @callback
    def async_reset_availability_cb(self) -> None:
        """Reset availability cb by cancelling the current and creating a new one."""
        self._attr_available = True
        if self._cancel_availability_cb:
            self._cancel_availability_cb()
        self.async_create_availability_cb()

    def async_create_availability_cb(self) -> None:
        """Create a availability cb and return the callback."""
        self._cancel_availability_cb = async_call_later(
            self.hass,
            get_unavailability_interval(self._ping_interval),
            self.async_set_unavailable,
        )

    @callback
    def async_set_unavailable(self, _) -> None:
        """Set unavailable."""
        self._attr_available = False
        self.async_write_ha_state()

    @property
    def device_info(self) -> DeviceInfo:
        """Return the device_info."""
        assert self._attr_name is not None
        assert self.unique_id is not None
        return {
            "name": self._attr_name,
            "identifiers": {(DOMAIN, self.unique_id)},
            "via_device": (DOMAIN, f"{self._port}_{self._account}"),
        }
