"""Contains time pickers exposed by the Starlink integration."""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from datetime import UTC, datetime, time, tzinfo
import math

from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import DOMAIN
from .coordinator import StarlinkData, StarlinkUpdateCoordinator
from .entity import StarlinkEntity


async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
    """Set up all time entities for this entry."""
    coordinator = hass.data[DOMAIN][entry.entry_id]

    async_add_entities(
        StarlinkTimeEntity(coordinator, description) for description in TIMES
    )


@dataclass(frozen=True, kw_only=True)
class StarlinkTimeEntityDescription(TimeEntityDescription):
    """Describes a Starlink time entity."""

    value_fn: Callable[[StarlinkData, tzinfo], time | None]
    update_fn: Callable[[StarlinkUpdateCoordinator, time], Awaitable[None]]
    available_fn: Callable[[StarlinkData], bool]


class StarlinkTimeEntity(StarlinkEntity, TimeEntity):
    """A TimeEntity for Starlink devices. Handles creating unique IDs."""

    entity_description: StarlinkTimeEntityDescription

    @property
    def native_value(self) -> time | None:
        """Return the value reported by the time."""
        return self.entity_description.value_fn(
            self.coordinator.data, self.coordinator.timezone
        )

    @property
    def available(self) -> bool:
        """Return True if entity is available."""
        return self.entity_description.available_fn(self.coordinator.data)

    async def async_set_value(self, value: time) -> None:
        """Change the time."""
        return await self.entity_description.update_fn(self.coordinator, value)


def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time:
    hour = math.floor(utc_minutes / 60)
    if hour > 23:
        hour -= 24
    minute = utc_minutes % 60
    try:
        utc = datetime.now(UTC).replace(
            hour=hour, minute=minute, second=0, microsecond=0
        )
    except ValueError as exc:
        raise HomeAssistantError from exc
    return utc.astimezone(timezone).time()


def _time_to_utc_minutes(t: time, timezone: tzinfo) -> int:
    try:
        zoned_time = datetime.now(timezone).replace(
            hour=t.hour, minute=t.minute, second=0, microsecond=0
        )
    except ValueError as exc:
        raise HomeAssistantError from exc
    utc_time = zoned_time.astimezone(UTC).time()
    return (utc_time.hour * 60) + utc_time.minute


TIMES = [
    StarlinkTimeEntityDescription(
        key="sleep_start",
        translation_key="sleep_start",
        value_fn=lambda data, timezone: _utc_minutes_to_time(data.sleep[0], timezone),
        update_fn=lambda coordinator, time: coordinator.async_set_sleep_start(
            _time_to_utc_minutes(time, coordinator.timezone)
        ),
        available_fn=lambda data: data.sleep[2],
    ),
    StarlinkTimeEntityDescription(
        key="sleep_end",
        translation_key="sleep_end",
        value_fn=lambda data, timezone: _utc_minutes_to_time(
            data.sleep[0] + data.sleep[1], timezone
        ),
        update_fn=lambda coordinator, time: coordinator.async_set_sleep_duration(
            _time_to_utc_minutes(time, coordinator.timezone)
        ),
        available_fn=lambda data: data.sleep[2],
    ),
]