# #region TaskGraphModule [C:5] [TYPE Module] [SEMANTICS task,graph,registry,persistence,crud]
# @BRIEF In-memory task registry with persistence-backed hydration, pagination, and
#        status/plugin filtering — extracted from the monolithic TaskManager.
# @LAYER Core
# @RELATION DEPENDS_ON -> [Task]
# @RELATION DEPENDS_ON -> [TaskPersistenceService]
# @RELATION DEPENDS_ON -> [TaskStatus]
# @RATIONALE Extracted from TaskManager to satisfy INV_7. TaskGraph owns the in-memory task
#            dictionary and provides task CRUD operations independent of execution, log flush,
#            or event subscription concerns.
# @REJECTED Keeping task registry inside TaskManager was rejected — it forces all five concerns
#            (registry, lifecycle, event bus, log flush, subscriptions) into one 708-line module
#            violating INV_7. TaskGraph is a pure data registry with persistence hydration.
# @PRE TaskPersistenceService is initialized and provides load_tasks / delete_tasks.
# @POST Task registry membership is keyed by unique task id and survives persistence hydration.
# @SIDE_EFFECT Mutates in-memory task dictionary; calls persistence on load/delete.
# @DATA_CONTRACT Input[task lifecycle events] -> Output[tasks:Dict[str, Task], task_futures:Dict[str, asyncio.Future]]
# @INVARIANT Registry membership is keyed by unique task id.

from datetime import UTC, datetime
from typing import Any

from .models import Task, TaskStatus
from .persistence import TaskPersistenceService


# #region TaskGraph [C:5] [TYPE Class] [SEMANTICS task,registry,crud,persistence,filter]
# @BRIEF In-memory task dependency graph spanning task registry nodes, pause futures,
#        and persistence-backed hydration.
# @RELATION DEPENDS_ON -> [Task]
# @RELATION DEPENDS_ON -> [TaskPersistenceService]
# @PRE TaskPersistenceService is available for load/delete operations.
# @POST Each live task id resolves to at most one active Task node and optional pause future.
# @INVARIANT Registry membership is keyed by unique task id.
# @RATIONALE Extracted as standalone registry class to decouple task CRUD from execution and
#            logging concerns. TaskGraph is a pure data structure with no async or I/O beyond
#            persistence hydration.
# @REJECTED Keeping task dict in TaskManager was rejected — it forced every operation to
#            navigate through the god class, preventing independent testing and violating INV_7.
class TaskGraph:
    """In-memory task registry with persistence-backed hydration and CRUD."""

    # #region __init__ [TYPE Function]
    # @BRIEF Initialize empty task registry with persistence service.
    def __init__(self, persistence_service: TaskPersistenceService):
        self.tasks: dict[str, Task] = {}
        self.task_futures: dict[str, Any] = {}
        self.persistence_service = persistence_service
    # #endregion __init__

    # #region load_persisted_tasks [C:3] [TYPE Function] [SEMANTICS persistence,load,hydration]
    # @BRIEF Load persisted tasks using persistence service.
    # @RELATION CALLS -> [EXT:frontend:TaskPersistenceService.load_tasks]
    def load_persisted_tasks(self, limit: int = 100) -> None:
        loaded_tasks = self.persistence_service.load_tasks(limit=limit)
        for task in loaded_tasks:
            if task.id not in self.tasks:
                self.tasks[task.id] = task
    # #endregion load_persisted_tasks

    # #region get_task [C:2] [TYPE Function] [SEMANTICS task,get,registry]
    # @BRIEF Retrieves a task by its ID.
    def get_task(self, task_id: str) -> Task | None:
        return self.tasks.get(task_id)
    # #endregion get_task

    # #region get_all_tasks [C:1] [TYPE Function]
    # @BRIEF Retrieves all registered tasks.
    def get_all_tasks(self) -> list[Task]:
        return list(self.tasks.values())
    # #endregion get_all_tasks

    # #region get_tasks [C:3] [TYPE Function] [SEMANTICS task,filter,paginate,sort]
    # @BRIEF Retrieves tasks with pagination and optional status/plugin filters.
    # @RELATION DEPENDS_ON -> [Task]
    def get_tasks(
        self,
        limit: int = 10,
        offset: int = 0,
        status: TaskStatus | None = None,
        plugin_ids: list[str] | None = None,
        completed_only: bool = False,
    ) -> list[Task]:
        tasks = list(self.tasks.values())
        if status:
            tasks = [t for t in tasks if t.status == status]
        if plugin_ids:
            plugin_id_set = set(plugin_ids)
            tasks = [t for t in tasks if t.plugin_id in plugin_id_set]
        if completed_only:
            tasks = [
                t for t in tasks if t.status in [TaskStatus.SUCCESS, TaskStatus.FAILED]
            ]
        # Sort by started_at descending with tolerant handling of mixed tz-aware/naive values.
        def sort_key(task: Task) -> float:
            started_at = task.started_at
            if started_at is None:
                return float("-inf")
            if not isinstance(started_at, datetime):
                return float("-inf")
            if started_at.tzinfo is None:
                return started_at.replace(tzinfo=UTC).timestamp()
            return started_at.timestamp()
        tasks.sort(key=sort_key, reverse=True)
        return tasks[offset : offset + limit]
    # #endregion get_tasks

    # #region add_task [C:2] [TYPE Function] [SEMANTICS task,register]
    # @BRIEF Register a task in the in-memory registry.
    def add_task(self, task: Task) -> None:
        self.tasks[task.id] = task
    # #endregion add_task

    # #region remove_tasks [C:3] [TYPE Function] [SEMANTICS task,remove,clear,persistence]
    # @BRIEF Remove tasks from registry and persistence, cancel futures for waiting tasks.
    # @RELATION CALLS -> [EXT:frontend:TaskPersistenceService.delete_tasks]
    def remove_tasks(self, task_ids: list[str]) -> int:
        for tid in task_ids:
            if tid in self.task_futures:
                self.task_futures[tid].cancel()
                del self.task_futures[tid]
            if tid in self.tasks:
                del self.tasks[tid]
        if task_ids:
            self.persistence_service.delete_tasks(task_ids)
        return len(task_ids)
    # #endregion remove_tasks

    # #region create_future [C:1] [TYPE Function]
    # @BRIEF Create and store a future for a paused task.
    def create_future(self, task_id: str, future: Any) -> None:
        self.task_futures[task_id] = future
    # #endregion create_future

    # #region resolve_future [C:1] [TYPE Function]
    # @BRIEF Resolve a paused task's future and remove it from the map.
    def resolve_future(self, task_id: str, result: bool = True) -> None:
        if task_id in self.task_futures:
            self.task_futures[task_id].set_result(result)
            del self.task_futures[task_id]
    # #endregion resolve_future

    # #region remove_future [C:1] [TYPE Function]
    # @BRIEF Remove a future without resolving it.
    def remove_future(self, task_id: str) -> None:
        if task_id in self.task_futures:
            del self.task_futures[task_id]
    # #endregion remove_future
# #endregion TaskGraph
# #endregion TaskGraphModule
