"""Support for Roborock time."""

from collections.abc import Callable, Coroutine
from dataclasses import dataclass
import datetime
from datetime import time
import logging
from typing import Any, override

from roborock.data import DnDTimer, ValleyElectricityTimer
from roborock.exceptions import RoborockException

from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from .const import DOMAIN
from .coordinator import (
    RoborockConfigEntry,
    RoborockCoordinatorType,
    RoborockDataUpdateCoordinator,
)
from .entity import RoborockEntityV1

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 0


@dataclass(frozen=True, kw_only=True)
class RoborockTimeDescription(TimeEntityDescription):
    """Class to describe a Roborock time entity."""

    trait: Callable[[Any], Any | None]
    """Function to determine if time entity is supported by the device."""

    get_value: Callable[[Any], datetime.time | None]
    """Function to get the value from the trait."""

    update_value: Callable[[Any, datetime.time], Coroutine[Any, Any, None]]
    """Function to set the value on the trait."""


TIME_DESCRIPTIONS: list[RoborockTimeDescription] = [
    RoborockTimeDescription(
        key="dnd_start_time",
        translation_key="dnd_start_time",
        trait=lambda api: api.dnd,
        update_value=lambda trait, desired_time: trait.set_dnd_timer(
            DnDTimer(
                enabled=trait.enabled,
                start_hour=desired_time.hour,
                start_minute=desired_time.minute,
                end_hour=trait.end_hour,
                end_minute=trait.end_minute,
            )
        ),
        get_value=lambda trait: trait.start_time,
        entity_category=EntityCategory.CONFIG,
    ),
    RoborockTimeDescription(
        key="dnd_end_time",
        translation_key="dnd_end_time",
        trait=lambda api: api.dnd,
        update_value=lambda trait, desired_time: trait.set_dnd_timer(
            DnDTimer(
                enabled=trait.enabled,
                start_hour=trait.start_hour,
                start_minute=trait.start_minute,
                end_hour=desired_time.hour,
                end_minute=desired_time.minute,
            )
        ),
        get_value=lambda trait: trait.end_time,
        entity_category=EntityCategory.CONFIG,
    ),
    RoborockTimeDescription(
        key="off_peak_start",
        translation_key="off_peak_start",
        trait=lambda api: api.valley_electricity_timer,
        update_value=lambda trait, desired_time: trait.set_timer(
            ValleyElectricityTimer(
                enabled=trait.enabled,
                start_hour=desired_time.hour,
                start_minute=desired_time.minute,
                end_hour=trait.end_hour,
                end_minute=trait.end_minute,
            )
        ),
        get_value=lambda trait: trait.start_time,
        entity_category=EntityCategory.CONFIG,
        entity_registry_enabled_default=False,
    ),
    RoborockTimeDescription(
        key="off_peak_end",
        translation_key="off_peak_end",
        trait=lambda api: api.valley_electricity_timer,
        update_value=lambda trait, desired_time: trait.set_timer(
            ValleyElectricityTimer(
                enabled=trait.enabled,
                start_hour=trait.start_hour,
                start_minute=trait.start_minute,
                end_hour=desired_time.hour,
                end_minute=desired_time.minute,
            )
        ),
        get_value=lambda trait: trait.end_time,
        entity_category=EntityCategory.CONFIG,
        entity_registry_enabled_default=False,
    ),
]


async def async_setup_entry(
    hass: HomeAssistant,
    config_entry: RoborockConfigEntry,
    async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
    """Set up Roborock time platform."""
    coordinators = config_entry.runtime_data

    @callback
    def async_add_coordinator_entities(
        coordinator: RoborockCoordinatorType,
    ) -> None:
        """Add entities for a specific coordinator."""
        if not isinstance(coordinator, RoborockDataUpdateCoordinator):
            return
        entities = [
            RoborockTimeEntity(
                f"{description.key}_{coordinator.duid_slug}",
                coordinator,
                description,
                trait,
            )
            for description in TIME_DESCRIPTIONS
            if (trait := description.trait(coordinator.properties_api)) is not None
        ]
        async_add_entities(entities)

    for coordinator in coordinators.values():
        async_add_coordinator_entities(coordinator)

    config_entry.async_on_unload(
        async_dispatcher_connect(
            hass,
            f"roborock_coordinator_added_{config_entry.entry_id}",
            async_add_coordinator_entities,
        )
    )


class RoborockTimeEntity(RoborockEntityV1, TimeEntity):
    """A class to set time options on a Roborock vacuum."""

    entity_description: RoborockTimeDescription

    def __init__(
        self,
        unique_id: str,
        coordinator: RoborockDataUpdateCoordinator,
        entity_description: RoborockTimeDescription,
        trait: Any,
    ) -> None:
        """Create a time entity."""
        self.entity_description = entity_description
        super().__init__(
            unique_id, coordinator.device_info, api=coordinator.properties_api.command
        )
        self._trait = trait

    @property
    @override
    def native_value(self) -> time | None:
        """Return the value reported by the time."""
        return self.entity_description.get_value(self._trait)

    @override
    async def async_set_value(self, value: time) -> None:
        """Set the time."""
        try:
            await self.entity_description.update_value(self._trait, value)
        except RoborockException as err:
            raise HomeAssistantError(
                translation_domain=DOMAIN,
                translation_key="update_options_failed",
            ) from err
