"""
Task Registry: Single source of truth for all task state.

Every task has a reference (⚡ for started, ✅ for completed).
The registry tracks all state - the LLM sees this injected into context.
"""

import secrets
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum


class TaskStatus(str, Enum):
    """Task lifecycle states."""
    QUEUED = "queued"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"


def make_reference(prefix: str = "⚡") -> str:
    """Generate a short cryptographic reference token."""
    return f"{prefix}{secrets.token_hex(4)}"


@dataclass
class Task:
    """A tracked task with full lifecycle state."""
    id: str
    reference: str  # ⚡abc123 (start) or ✅def456 (completion)
    description: str
    label: str
    status: TaskStatus = TaskStatus.QUEUED
    
    # Origin info for routing responses
    origin_channel: str = "cli"
    origin_chat_id: str = "direct"
    
    # Timestamps
    created_at: datetime = field(default_factory=datetime.now)
    started_at: datetime | None = None
    completed_at: datetime | None = None
    
    # Progress tracking
    current_action: str = ""
    actions_completed: list[str] = field(default_factory=list)
    iteration: int = 0
    max_iterations: int = 15
    
    # Results
    result: str | None = None
    error: str | None = None
    completion_reference: str | None = None  # ✅ reference on completion
    
    def to_progress_summary(self) -> str:
        """Short progress summary for context injection."""
        elapsed = datetime.now() - self.created_at
        mins, secs = divmod(int(elapsed.total_seconds()), 60)
        time_str = f"{mins}m {secs}s" if mins else f"{secs}s"
        
        if self.status == TaskStatus.RUNNING:
            action_str = f", currently: {self.current_action}" if self.current_action else ""
            return f"{self.reference}: \"{self.label}\" - step {self.iteration}/{self.max_iterations}{action_str} ({time_str})"
        elif self.status == TaskStatus.QUEUED:
            return f"{self.reference}: \"{self.label}\" - queued ({time_str})"
        elif self.status == TaskStatus.COMPLETED:
            ref = self.completion_reference or self.reference
            return f"{ref}: \"{self.label}\" - completed ({time_str})"
        elif self.status == TaskStatus.FAILED:
            return f"{self.reference}: \"{self.label}\" - failed: {self.error or 'unknown'}"
        else:
            return f"{self.reference}: \"{self.label}\" - {self.status.value}"
    
    def to_full_summary(self) -> str:
        """Full summary for status requests."""
        lines = [
            f"Task: {self.label}",
            f"Reference: {self.reference}",
            f"Status: {self.status.value}",
        ]
        
        if self.completion_reference:
            lines.append(f"Completion: {self.completion_reference}")
        
        elapsed = (self.completed_at or datetime.now()) - self.created_at
        mins, secs = divmod(int(elapsed.total_seconds()), 60)
        time_str = f"{mins}m {secs}s" if mins else f"{secs}s"
        lines.append(f"Time: {time_str}")
        
        if self.status == TaskStatus.RUNNING:
            lines.append(f"Progress: step {self.iteration}/{self.max_iterations}")
            if self.current_action:
                lines.append(f"Currently: {self.current_action}")
            if self.actions_completed:
                recent = self.actions_completed[-5:]
                lines.append(f"Recent: {', '.join(recent)}")
        
        if self.result and self.status == TaskStatus.COMPLETED:
            preview = self.result[:300] + ("..." if len(self.result) > 300 else "")
            lines.append(f"Result: {preview}")
        
        if self.error:
            lines.append(f"Error: {self.error}")
        
        return "\n".join(lines)


class TaskRegistry:
    """
    Central registry for all tasks.
    
    This is the single source of truth. The LLM sees this state
    injected into every prompt, so it's always omniscient.
    """
    
    def __init__(self):
        self._tasks: dict[str, Task] = {}
        self._ref_to_id: dict[str, str] = {}  # Map references to task IDs
        self._completed_cache: list[str] = []  # Recent completed task IDs
        self._max_completed_cache = 10
    
    def create(
        self,
        description: str,
        label: str,
        origin_channel: str = "cli",
        origin_chat_id: str = "direct",
    ) -> Task:
        """Create a new task and return it."""
        task_id = secrets.token_hex(4)
        reference = make_reference("⚡")
        
        task = Task(
            id=task_id,
            reference=reference,
            description=description,
            label=label,
            origin_channel=origin_channel,
            origin_chat_id=origin_chat_id,
        )
        
        self._tasks[task_id] = task
        self._ref_to_id[reference] = task_id
        self._ref_to_id[reference[1:]] = task_id  # Also map bare hex
        
        return task
    
    def get(self, task_id: str) -> Task | None:
        """Get a task by ID."""
        return self._tasks.get(task_id)
    
    def get_by_ref(self, ref: str) -> Task | None:
        """Get a task by reference (⚡abc123 or just abc123)."""
        clean_ref = ref.strip()
        if clean_ref.startswith("⚡") or clean_ref.startswith("✅"):
            clean_ref = clean_ref[1:]
        
        task_id = self._ref_to_id.get(clean_ref) or self._ref_to_id.get(f"⚡{clean_ref}")
        if task_id:
            return self._tasks.get(task_id)
        return None
    
    def mark_started(self, task_id: str) -> None:
        """Mark a task as started."""
        task = self._tasks.get(task_id)
        if task:
            task.status = TaskStatus.RUNNING
            task.started_at = datetime.now()
    
    def mark_completed(self, task_id: str, result: str) -> None:
        """Mark a task as completed with result."""
        task = self._tasks.get(task_id)
        if task:
            task.status = TaskStatus.COMPLETED
            task.completed_at = datetime.now()
            task.result = result
            task.completion_reference = make_reference("✅")
            task.current_action = ""
            
            self._ref_to_id[task.completion_reference] = task_id
            self._ref_to_id[task.completion_reference[1:]] = task_id
            
            self._completed_cache.append(task_id)
            if len(self._completed_cache) > self._max_completed_cache:
                self._completed_cache.pop(0)
    
    def mark_failed(self, task_id: str, error: str) -> None:
        """Mark a task as failed with error."""
        task = self._tasks.get(task_id)
        if task:
            task.status = TaskStatus.FAILED
            task.completed_at = datetime.now()
            task.error = error
            task.completion_reference = make_reference("✅")
            task.current_action = ""
            
            self._ref_to_id[task.completion_reference] = task_id
            self._ref_to_id[task.completion_reference[1:]] = task_id
    
    def update_progress(
        self,
        task_id: str,
        iteration: int | None = None,
        current_action: str | None = None,
        action_completed: str | None = None,
    ) -> None:
        """Update task progress."""
        task = self._tasks.get(task_id)
        if task:
            if iteration is not None:
                task.iteration = iteration
            if current_action is not None:
                task.current_action = current_action
            if action_completed:
                task.actions_completed.append(action_completed)
    
    def get_active_tasks(self) -> list[Task]:
        """Get all active (queued or running) tasks."""
        return [
            t for t in self._tasks.values()
            if t.status in (TaskStatus.QUEUED, TaskStatus.RUNNING)
        ]
    
    def get_recent_completed(self, limit: int = 5) -> list[Task]:
        """Get recently completed tasks."""
        recent_ids = self._completed_cache[-limit:]
        return [self._tasks[tid] for tid in recent_ids if tid in self._tasks]
    
    def has_active_tasks(self) -> bool:
        """Check if any tasks are active."""
        return any(
            t.status in (TaskStatus.QUEUED, TaskStatus.RUNNING)
            for t in self._tasks.values()
        )
    
    def get_context_summary(self) -> str:
        """
        Get a summary of current state for context injection.
        This is what the LLM sees in every prompt - makes it omniscient.
        """
        active = self.get_active_tasks()
        recent = self.get_recent_completed(3)
        
        if not active and not recent:
            return "No active or recent tasks."
        
        lines = []
        
        if active:
            lines.append("Active tasks:")
            for task in active:
                lines.append(f"  • {task.to_progress_summary()}")
        
        if recent:
            lines.append("Recently completed:")
            for task in recent:
                lines.append(f"  • {task.to_progress_summary()}")
        
        return "\n".join(lines)
    
    def get_status_for_ref(self, ref: str | None) -> str:
        """Get status for a specific reference or all tasks."""
        if ref:
            task = self.get_by_ref(ref)
            if task:
                return task.to_full_summary()
            return f"No task found with reference '{ref}'"
        
        active = self.get_active_tasks()
        recent = self.get_recent_completed(5)
        
        if not active and not recent:
            return "No tasks to report on."
        
        parts = []
        if active:
            parts.append("=== Active ===")
            for task in active:
                parts.append(task.to_full_summary())
                parts.append("")
        
        if recent:
            parts.append("=== Recent ===")
            for task in recent:
                parts.append(task.to_full_summary())
                parts.append("")
        
        return "\n".join(parts).strip()
