"""To-do platform for the Maintenance Supporter integration.

A single global ``todo`` list entity aggregates every active maintenance task
across all objects, so maintenance shows up in Home Assistant's native **To-do**
card and is reachable through **Assist/voice** ("what maintenance is due?",
"mark the filter change done").

Each task becomes one to-do item whose status mirrors its due state: a task
that is due-soon / overdue / triggered is ``needs_action`` (actionable), an
up-to-date task is ``completed``. Checking a ``needs_action`` item off routes to
the same ``complete_maintenance`` coordinator method the panel, buttons, and
notification actions use — one action layer, no duplicated logic.
"""

from __future__ import annotations

import logging
from datetime import date
from typing import TYPE_CHECKING

from homeassistant.components.todo import TodoItem, TodoListEntity
from homeassistant.components.todo.const import (
    TodoItemStatus,
    TodoListEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from .const import (
    CONF_OBJECT,
    CONF_TASK_ENABLED,
    CONF_TASKS,
    DOMAIN,
    GLOBAL_UNIQUE_ID,
    MaintenanceStatus,
)

if TYPE_CHECKING:
    from . import MaintenanceSupporterConfigEntry

_LOGGER = logging.getLogger(__name__)

PARALLEL_UPDATES = 0

# Statuses that make a task "actionable" in the to-do list (needs_action).
_ACTION_STATUSES = frozenset(
    {
        MaintenanceStatus.DUE_SOON,
        MaintenanceStatus.OVERDUE,
        MaintenanceStatus.TRIGGERED,
    }
)


async def async_setup_entry(
    hass: HomeAssistant,
    entry: MaintenanceSupporterConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """Set up the single global maintenance to-do list.

    Mirrors the calendar platform: the list lives on the global entry and
    aggregates every object's coordinator. Object entries register their
    coordinator so the list repaints when a task's status changes.
    """
    if entry.unique_id != GLOBAL_UNIQUE_ID:
        runtime_data = entry.runtime_data
        if runtime_data and runtime_data.coordinator:
            todo = hass.data.get(DOMAIN, {}).get("_todo_entity")
            if todo:
                runtime_data.coordinator.register_todo_entity(todo)
        return

    todo = MaintenanceTodoList(hass)
    hass.data.setdefault(DOMAIN, {})["_todo_entity"] = todo
    async_add_entities([todo])

    for other_entry in hass.config_entries.async_entries(DOMAIN):
        if other_entry.unique_id == GLOBAL_UNIQUE_ID:
            continue
        other_data = getattr(other_entry, "runtime_data", None)
        if other_data and getattr(other_data, "coordinator", None):
            other_data.coordinator.register_todo_entity(todo)

    _LOGGER.debug("Maintenance to-do list entity created")


class MaintenanceTodoList(TodoListEntity):
    """A single list of every active maintenance task, keyed by due state."""

    _attr_name = "Maintenance"
    _attr_unique_id = "maintenance_supporter_todo"
    _attr_translation_key = "maintenance_todo"
    _attr_should_poll = False
    _attr_supported_features = TodoListEntityFeature.UPDATE_TODO_ITEM

    def __init__(self, hass: HomeAssistant) -> None:
        """Initialize the to-do list."""
        self._hass = hass

    def refresh(self) -> None:
        """Repaint the list (called by coordinators on update)."""
        if self.hass is not None:
            self.async_write_ha_state()

    @property
    def todo_items(self) -> list[TodoItem]:
        """Build one item per active task across all objects."""
        items: list[TodoItem] = []
        for entry in self._hass.config_entries.async_entries(DOMAIN):
            if entry.unique_id == GLOBAL_UNIQUE_ID:
                continue
            obj_data = entry.data.get(CONF_OBJECT, {})
            if obj_data.get("archived_at") is not None:
                continue
            obj_name = obj_data.get("name", "Unknown")

            runtime_data = getattr(entry, "runtime_data", None)
            coordinator = getattr(runtime_data, "coordinator", None)
            live = coordinator.data.get(CONF_TASKS, {}) if coordinator and coordinator.data else {}

            for task_id, task_cfg in entry.data.get(CONF_TASKS, {}).items():
                if not task_cfg.get(CONF_TASK_ENABLED, True):
                    continue
                if task_cfg.get("archived_at") is not None:
                    continue
                task_live = live.get(task_id, {})
                status = task_live.get("_status", MaintenanceStatus.OK)
                # Archived is retired; paused (v2.20, N3) is frozen — neither
                # belongs on the actionable To-do list.
                if status in (MaintenanceStatus.ARCHIVED, MaintenanceStatus.PAUSED):
                    continue
                due_iso = task_live.get("_next_due")
                due: date | None = None
                if isinstance(due_iso, str):
                    try:
                        due = date.fromisoformat(due_iso)
                    except ValueError:
                        due = None
                items.append(
                    TodoItem(
                        uid=f"{entry.entry_id}:{task_id}",
                        summary=f"{obj_name}: {task_cfg.get('name', '')}",
                        status=(TodoItemStatus.NEEDS_ACTION if status in _ACTION_STATUSES else TodoItemStatus.COMPLETED),
                        due=due,
                    )
                )
        return items

    async def async_update_todo_item(self, item: TodoItem) -> None:
        """Handle a checkbox toggle: completing an item completes the task.

        Un-checking (back to needs_action) is a no-op — a maintenance task can't
        be "un-completed"; its next cycle is driven by the schedule.
        """
        if item.status != TodoItemStatus.COMPLETED or not item.uid:
            return
        entry_id, _, task_id = item.uid.partition(":")
        entry = self._hass.config_entries.async_get_entry(entry_id)
        if entry is None:
            return
        coordinator = getattr(getattr(entry, "runtime_data", None), "coordinator", None)
        if coordinator is None:
            return
        # Respect a task's completion window — silently no-op (the item stays
        # needs_action) rather than completing an item that's not yet due.
        from .models.maintenance_task import MaintenanceTask

        td = coordinator._get_merged_tasks_data().get(task_id)
        if td and not MaintenanceTask.from_dict(td).can_complete_now:
            return
        await coordinator.complete_maintenance(
            task_id, notes="Completed from the To-do list", unattended=True
        )
        self.async_write_ha_state()
