"""TaskManager — gestor de tareas background thread-safe con patron singleton."""

import threading
import uuid
from datetime import datetime, timezone

from task_status import TaskStatus
from task import Task


class TaskManager:
    """Gestor de tareas en memoria, thread-safe, con patron singleton.

    Uso tipico:
        manager = TaskManager()
        task_id = manager.create_task("graph_build", metadata={"nodes": 500})
        # en un hilo background:
        manager.update_task(task_id, progress=50, message="Procesando nodos...")
        manager.complete_task(task_id, result={"graph_id": "abc"})
        # desde el frontend (polling):
        task = manager.get_task(task_id)
    """

    _instance: "TaskManager | None" = None
    _lock: threading.Lock = threading.Lock()

    def __new__(cls) -> "TaskManager":
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    instance = super().__new__(cls)
                    instance._tasks: dict[str, Task] = {}
                    instance._tasks_lock = threading.Lock()
                    cls._instance = instance
        return cls._instance

    # ------------------------------------------------------------------
    # Operaciones publicas
    # ------------------------------------------------------------------

    def create_task(self, task_type: str, metadata: dict | None = None) -> str:
        """Crea una nueva tarea en estado PENDING y retorna su task_id.

        Args:
            task_type: Identificador libre del tipo de tarea ("graph_build", etc.).
            metadata:  Datos adicionales asociados a la tarea (parametros de entrada).

        Returns:
            task_id — UUID string de la tarea creada.
        """
        task_id = str(uuid.uuid4())
        now = datetime.now(timezone.utc)
        task = Task(
            task_id=task_id,
            task_type=task_type,
            status=TaskStatus.PENDING,
            created_at=now,
            updated_at=now,
            metadata=metadata or {},
        )
        with self._tasks_lock:
            self._tasks[task_id] = task
        return task_id

    def get_task(self, task_id: str) -> Task | None:
        """Retorna la tarea con el id dado, o None si no existe.

        Args:
            task_id: UUID de la tarea.

        Returns:
            Task o None.
        """
        with self._tasks_lock:
            return self._tasks.get(task_id)

    def update_task(
        self,
        task_id: str,
        status: TaskStatus | None = None,
        progress: int | None = None,
        message: str | None = None,
        result: dict | None = None,
        error: str | None = None,
        progress_detail: dict | None = None,
    ) -> None:
        """Actualiza campos de una tarea existente.

        Solo actualiza los campos que no sean None. Actualiza siempre updated_at.

        Args:
            task_id:         UUID de la tarea a actualizar.
            status:          Nuevo estado (TaskStatus), opcional.
            progress:        Nuevo porcentaje 0-100, opcional.
            message:         Nuevo mensaje de estado, opcional.
            result:          Resultado de la tarea, opcional.
            error:           Mensaje de error, opcional.
            progress_detail: Detalles granulares de progreso, opcional.
        """
        with self._tasks_lock:
            task = self._tasks.get(task_id)
            if task is None:
                return
            if status is not None:
                task.status = status
            if progress is not None:
                task.progress = progress
            if message is not None:
                task.message = message
            if result is not None:
                task.result = result
            if error is not None:
                task.error = error
            if progress_detail is not None:
                task.progress_detail = progress_detail
            task.updated_at = datetime.now(timezone.utc)

    def complete_task(self, task_id: str, result: dict) -> None:
        """Marca la tarea como COMPLETED con el resultado dado.

        Args:
            task_id: UUID de la tarea.
            result:  Resultado serializable de la tarea completada.
        """
        self.update_task(
            task_id,
            status=TaskStatus.COMPLETED,
            progress=100,
            result=result,
        )

    def fail_task(self, task_id: str, error: str) -> None:
        """Marca la tarea como FAILED con el mensaje de error dado.

        Args:
            task_id: UUID de la tarea.
            error:   Descripcion del error.
        """
        self.update_task(
            task_id,
            status=TaskStatus.FAILED,
            error=error,
        )

    def list_tasks(self, task_type: str | None = None) -> list[dict]:
        """Lista todas las tareas, opcionalmente filtradas por tipo.

        Args:
            task_type: Si se especifica, solo retorna tareas de ese tipo.

        Returns:
            Lista de dicts serializados (via Task.to_dict()).
        """
        with self._tasks_lock:
            tasks = list(self._tasks.values())
        if task_type is not None:
            tasks = [t for t in tasks if t.task_type == task_type]
        return [t.to_dict() for t in tasks]

    def cleanup_old_tasks(self, max_age_hours: int = 24) -> None:
        """Elimina tareas terminadas (COMPLETED o FAILED) con mas de max_age_hours horas.

        Args:
            max_age_hours: Edad maxima en horas para tareas terminadas. Default: 24.
        """
        now = datetime.now(timezone.utc)
        terminal = {TaskStatus.COMPLETED, TaskStatus.FAILED}
        with self._tasks_lock:
            to_delete = [
                tid
                for tid, task in self._tasks.items()
                if task.status in terminal
                and (now - task.updated_at).total_seconds() > max_age_hours * 3600
            ]
            for tid in to_delete:
                del self._tasks[tid]
