"""Coordinator to handle keeping device states up to date."""

from __future__ import annotations

from datetime import timedelta
import logging
import time

from pycync import Cync, CyncDevice, User
from pycync.exceptions import AuthFailedError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ACCESS_TOKEN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import CONF_EXPIRES_AT, CONF_REFRESH_TOKEN

_LOGGER = logging.getLogger(__name__)

type CyncConfigEntry = ConfigEntry[CyncCoordinator]


class CyncCoordinator(DataUpdateCoordinator[dict[int, CyncDevice]]):
    """Coordinator to handle updating Cync device states."""

    config_entry: CyncConfigEntry

    def __init__(
        self, hass: HomeAssistant, config_entry: CyncConfigEntry, cync: Cync
    ) -> None:
        """Initialize the Cync coordinator."""
        super().__init__(
            hass,
            _LOGGER,
            name="Cync Data Coordinator",
            config_entry=config_entry,
            update_interval=timedelta(seconds=30),
            always_update=True,
        )
        self.cync = cync

    async def on_data_update(self, data: dict[int, CyncDevice]) -> None:
        """Update registered devices with new data."""
        merged_data = self.data | data if self.data else data
        self.async_set_updated_data(merged_data)

    async def _async_setup(self) -> None:
        """Set up the coordinator with initial device states."""
        logged_in_user = self.cync.get_logged_in_user()
        if logged_in_user.access_token != self.config_entry.data[CONF_ACCESS_TOKEN]:
            await self._update_config_cync_credentials(logged_in_user)

    async def _async_update_data(self) -> dict[int, CyncDevice]:
        """First, refresh the user's auth token if it is set to expire in less than one hour.

        Then, fetch all current device states.
        """

        logged_in_user = self.cync.get_logged_in_user()
        if logged_in_user.expires_at - time.time() < 3600:
            await self._async_refresh_cync_credentials()

        self.cync.update_device_states()
        current_device_states = self.cync.get_devices()

        return {device.device_id: device for device in current_device_states}

    async def _async_refresh_cync_credentials(self) -> None:
        """Attempt to refresh the Cync user's authentication token."""

        try:
            refreshed_user = await self.cync.refresh_credentials()
        except AuthFailedError as ex:
            raise ConfigEntryAuthFailed("Unable to refresh user token") from ex
        else:
            await self._update_config_cync_credentials(refreshed_user)

    async def _update_config_cync_credentials(self, user_info: User) -> None:
        """Update the config entry with current user info."""

        new_data = {**self.config_entry.data}
        new_data[CONF_ACCESS_TOKEN] = user_info.access_token
        new_data[CONF_REFRESH_TOKEN] = user_info.refresh_token
        new_data[CONF_EXPIRES_AT] = user_info.expires_at
        self.hass.config_entries.async_update_entry(self.config_entry, data=new_data)
