"""DataUpdateCoordinator for the Sensibo integration."""

from datetime import timedelta
from typing import TYPE_CHECKING, override

from pysensibo import SensiboClient
from pysensibo.exceptions import AuthenticationError, SensiboError
from pysensibo.model import SensiboData

from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DEFAULT_SCAN_INTERVAL, DOMAIN, LOGGER, TIMEOUT

if TYPE_CHECKING:
    from . import SensiboConfigEntry

REQUEST_REFRESH_DELAY = 0.35


class SensiboDataUpdateCoordinator(DataUpdateCoordinator[SensiboData]):
    """A Sensibo Data Update Coordinator."""

    config_entry: SensiboConfigEntry

    def __init__(self, hass: HomeAssistant, config_entry: SensiboConfigEntry) -> None:
        """Initialize the Sensibo coordinator."""
        super().__init__(
            hass,
            LOGGER,
            config_entry=config_entry,
            name=DOMAIN,
            update_interval=timedelta(seconds=DEFAULT_SCAN_INTERVAL),
            # We don't want an immediate refresh since the device
            # takes a moment to reflect the state change
            request_refresh_debouncer=Debouncer(
                hass, LOGGER, cooldown=REQUEST_REFRESH_DELAY, immediate=False
            ),
        )
        self.client = SensiboClient(
            self.config_entry.data[CONF_API_KEY],
            session=async_get_clientsession(hass),
            timeout=TIMEOUT,
        )
        self.previous_devices: set[str] = set()

    def get_devices(
        self, added_devices: set[str]
    ) -> tuple[set[str], set[str], set[str]]:
        """Addition and removal of devices."""
        data = self.data
        current_motion_sensors = {
            sensor_id
            for device_data in data.parsed.values()
            if device_data.motion_sensors
            for sensor_id in device_data.motion_sensors
        }
        current_devices: set[str] = set(data.parsed)
        LOGGER.debug(
            "Current devices: %s, moption sensors: %s",
            current_devices,
            current_motion_sensors,
        )
        new_devices: set[str] = (
            current_motion_sensors | current_devices
        ) - added_devices
        remove_devices = added_devices - current_devices - current_motion_sensors
        new_added_devices = (added_devices - remove_devices) | new_devices

        LOGGER.debug(
            "New devices: %s, Removed devices: %s, Added devices: %s",
            new_devices,
            remove_devices,
            new_added_devices,
        )
        return (new_devices, remove_devices, new_added_devices)

    @override
    async def _async_update_data(self) -> SensiboData:
        """Fetch data from Sensibo."""
        try:
            data = await self.client.async_get_devices_data()
        except AuthenticationError as error:
            raise ConfigEntryAuthFailed(
                translation_domain=DOMAIN,
                translation_key="auth_error",
            ) from error
        except SensiboError as error:
            raise UpdateFailed(
                translation_domain=DOMAIN,
                translation_key="update_error",
                translation_placeholders={"error": str(error)},
            ) from error

        if not data.raw:
            raise UpdateFailed(translation_domain=DOMAIN, translation_key="no_data")

        current_devices = set(data.parsed)
        for device_data in data.parsed.values():
            if device_data.motion_sensors:
                for motion_sensor_id in device_data.motion_sensors:
                    current_devices.add(motion_sensor_id)

        if stale_devices := self.previous_devices - current_devices:
            LOGGER.debug("Removing stale devices: %s", stale_devices)
            device_registry = dr.async_get(self.hass)
            for _id in stale_devices:
                device = device_registry.async_get_device_by_identifier(
                    (DOMAIN, _id), self.config_entry.entry_id
                )
                if device:
                    device_registry.async_remove_device(device.id)
        self.previous_devices = current_devices

        return data
