"""Data update coordinator for the Radarr integration."""

from abc import ABC, abstractmethod
import asyncio
from dataclasses import dataclass
from datetime import date, datetime, timedelta
from typing import Generic, TypeVar, cast, override

from aiopyarr import (
    Health,
    RadarrCalendarItem,
    RadarrMovie,
    RootFolder,
    SystemStatus,
    exceptions,
)
from aiopyarr.models.host_configuration import PyArrHostConfiguration
from aiopyarr.radarr_client import RadarrClient

from homeassistant.components.calendar import CalendarEvent
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, LOGGER


@dataclass(kw_only=True, slots=True)
class RadarrData:
    """Radarr data type."""

    calendar: CalendarUpdateCoordinator
    disk_space: DiskSpaceDataUpdateCoordinator
    health: HealthDataUpdateCoordinator
    movie: MoviesDataUpdateCoordinator
    queue: QueueDataUpdateCoordinator
    status: StatusDataUpdateCoordinator


type RadarrConfigEntry = ConfigEntry[RadarrData]

T = TypeVar("T", bound=SystemStatus | list[RootFolder] | list[Health] | int | None)


@dataclass
class RadarrEventMixIn:
    """Mixin for Radarr calendar event."""

    release_type: str


@dataclass
class RadarrEvent(CalendarEvent, RadarrEventMixIn):
    """A class to describe a Radarr calendar event."""


class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], ABC, Generic[T]):  # noqa: UP046
    """Data update coordinator for the Radarr integration."""

    config_entry: RadarrConfigEntry
    _update_interval = timedelta(seconds=30)

    def __init__(
        self,
        hass: HomeAssistant,
        config_entry: RadarrConfigEntry,
        host_configuration: PyArrHostConfiguration,
        api_client: RadarrClient,
    ) -> None:
        """Initialize the coordinator."""
        super().__init__(
            hass=hass,
            logger=LOGGER,
            config_entry=config_entry,
            name=DOMAIN,
            update_interval=self._update_interval,
        )
        self.api_client = api_client
        self.host_configuration = host_configuration

    @override
    async def _async_update_data(self) -> T:
        """Get the latest data from Radarr."""
        try:
            return await self._fetch_data()

        except exceptions.ArrConnectionException as ex:
            raise UpdateFailed(ex) from ex
        except exceptions.ArrAuthenticationException as ex:
            raise ConfigEntryAuthFailed(
                "API Key is no longer valid. Please reauthenticate"
            ) from ex

    @abstractmethod
    async def _fetch_data(self) -> T:
        """Fetch the actual data."""
        raise NotImplementedError


class StatusDataUpdateCoordinator(RadarrDataUpdateCoordinator[SystemStatus]):
    """Status update coordinator for Radarr."""

    @override
    async def _fetch_data(self) -> SystemStatus:
        """Fetch the data."""
        return await self.api_client.async_get_system_status()


class DiskSpaceDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[RootFolder]]):
    """Disk space update coordinator for Radarr."""

    @override
    async def _fetch_data(self) -> list[RootFolder]:
        """Fetch the data."""
        root_folders = await self.api_client.async_get_root_folders()
        if isinstance(root_folders, RootFolder):
            return [root_folders]
        return root_folders


class HealthDataUpdateCoordinator(RadarrDataUpdateCoordinator[list[Health]]):
    """Health update coordinator."""

    @override
    async def _fetch_data(self) -> list[Health]:
        """Fetch the health data."""
        health = await self.api_client.async_get_failed_health_checks()
        if isinstance(health, Health):
            return [health]
        return health


class MoviesDataUpdateCoordinator(RadarrDataUpdateCoordinator[int]):
    """Movies count update coordinator."""

    @override
    async def _fetch_data(self) -> int:
        """Fetch the total count of movies in Radarr."""
        return len(cast(list[RadarrMovie], await self.api_client.async_get_movies()))


class QueueDataUpdateCoordinator(RadarrDataUpdateCoordinator[int]):
    """Queue count update coordinator."""

    @override
    async def _fetch_data(self) -> int:
        """Fetch the number of movies in the download queue."""
        # page_size=1 is sufficient since we only need the totalRecords count
        return (await self.api_client.async_get_queue(page_size=1)).totalRecords


class CalendarUpdateCoordinator(RadarrDataUpdateCoordinator[None]):
    """Calendar update coordinator."""

    _update_interval = timedelta(hours=1)

    def __init__(
        self,
        hass: HomeAssistant,
        config_entry: RadarrConfigEntry,
        host_configuration: PyArrHostConfiguration,
        api_client: RadarrClient,
    ) -> None:
        """Initialize."""
        super().__init__(hass, config_entry, host_configuration, api_client)
        self.event: RadarrEvent | None = None
        self._events: list[RadarrEvent] = []

    @override
    async def _fetch_data(self) -> None:
        """Fetch the calendar."""
        self.event = None
        _date = datetime.today()
        while self.event is None:
            await self.async_get_events(_date, _date + timedelta(days=1))
            for event in self._events:
                if event.start >= _date.date():
                    self.event = event
                    break
            # Prevent infinite loop in case there is nothing recent in the calendar
            if (_date - datetime.today()).days > 45:
                break
            _date = _date + timedelta(days=1)

    async def async_get_events(
        self, start_date: datetime, end_date: datetime
    ) -> list[RadarrEvent]:
        """Get cached events and request missing dates."""
        # remove older events to prevent memory leak
        self._events = [
            e
            for e in self._events
            if e.start >= datetime.now().date() - timedelta(days=30)  # pylint: disable=home-assistant-enforce-naive-now
        ]
        _days = (end_date - start_date).days
        await asyncio.gather(
            *(
                self._async_get_events(d)
                for d in ((start_date + timedelta(days=x)).date() for x in range(_days))
                if d not in (event.start for event in self._events)
            )
        )
        return self._events

    async def _async_get_events(self, _date: date) -> None:
        """Return events from specified date."""
        self._events.extend(
            _get_calendar_event(evt)
            for evt in await self.api_client.async_get_calendar(
                start_date=_date, end_date=_date + timedelta(days=1)
            )
            if evt.title not in (e.summary for e in self._events)
        )


def _get_calendar_event(event: RadarrCalendarItem) -> RadarrEvent:
    """Return a RadarrEvent from an API event."""
    _date, _type = event.releaseDateType()
    return RadarrEvent(
        summary=event.title,
        start=_date - timedelta(days=1),
        end=_date,
        description=event.overview.replace(":", ";"),
        release_type=_type,
    )
