"""MCP tool implementations for ccontext."""

from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional

from .schema import (
    Context,
    ContextResponse,
    EntryType,
    Note,
    Reference,
    Step,
    StepInput,
    StepStatus,
    Task,
    TasksSummary,
    TaskStatus,
)
from .storage import ContextStorage


# Tool descriptions - these are critical for Agent adoption
TOOL_DESCRIPTIONS = {
    "get_context": (
        "**Start here!** Get the project's execution context before doing any work. "
        "Returns current goal, decision rationale (why), what other agents are doing, "
        "active tasks, and important notes/warnings. Call this first to understand "
        "the full picture and avoid duplicating work or missing critical context."
    ),
    "update_context": (
        "Update your status so other agents know what you're working on. "
        "Also use this to update the project goal or decision rationale (why) "
        "when direction changes."
    ),
    "list_tasks": (
        "Query tasks by status (planned/active/done) or assignee. "
        "Use to find what needs to be done or check progress."
    ),
    "create_task": (
        "Create a new task when starting work that involves 3+ files or multiple steps. "
        "Tasks help track progress and coordinate with other agents."
    ),
    "update_task": (
        "Update task status or mark steps as done. "
        "Keep tasks current so others can see real progress."
    ),
    "delete_task": (
        "Remove a task that's no longer needed (cancelled or created by mistake)."
    ),
    "add_entry": (
        "Record important discoveries, lessons learned, or warnings that future work "
        "should know about. Also record useful file locations (references). "
        "High-score entries persist longer."
    ),
    "update_entry": (
        "Update a note/reference or renew its score to keep it visible longer. "
        "Use when information is still relevant but score is low."
    ),
    "remove_entry": (
        "Delete a note or reference that's no longer relevant. "
        "Use this to clean up outdated information that shouldn't persist in the context."
    ),
}


class ContextTools:
    """Implementation of ccontext MCP tools."""

    def __init__(self, root: Optional[Path] = None):
        """Initialize tools with project root directory."""
        self.storage = ContextStorage(root)

    # === Context Tools ===

    def get_context(self, agent_id: Optional[str] = None) -> dict[str, Any]:
        """Get the full execution context.
        
        This is the core tool. Each call triggers score decay for all entries.
        
        Args:
            agent_id: Optional agent identifier (for logging/tracking)
            
        Returns:
            ContextResponse as dict
        """
        # Load context
        context = self.storage.load_context()
        
        # Decay scores and archive if needed
        context, archived_notes, archived_refs = self.storage.decay_scores(context)
        
        # Archive entries that hit threshold
        if archived_notes or archived_refs:
            self.storage.archive_entries(archived_notes, archived_refs)
        
        # Save updated context (with decayed scores)
        self.storage.save_context(context)
        
        # Archive old tasks
        self.storage.archive_old_tasks()
        
        # Build response
        tasks = self.storage.list_tasks(include_archived=False)
        
        # Calculate summary
        summary = TasksSummary(
            total=len(tasks),
            done=len([t for t in tasks if t.status == TaskStatus.DONE]),
            active=len([t for t in tasks if t.status == TaskStatus.ACTIVE]),
            planned=len([t for t in tasks if t.status == TaskStatus.PLANNED]),
        )
        
        # Find active task
        active_task = None
        for task in tasks:
            if task.status == TaskStatus.ACTIVE:
                active_task = task
                break
        
        # Build notes list with expiring flag
        notes_list = [
            {
                "id": n.id,
                "score": n.score,
                "content": n.content,
                "expiring": n.expiring,
            }
            for n in context.notes
        ]
        
        # Build references list with expiring flag
        refs_list = [
            {
                "id": r.id,
                "score": r.score,
                "uri": r.uri,
                "note": r.note,
                "expiring": r.expiring,
            }
            for r in context.references
        ]
        
        response = ContextResponse(
            goal=context.goal,
            why=context.why,
            agents=context.agents,
            notes=notes_list,
            references=refs_list,
            tasks_summary=summary,
            active_task=active_task,
        )
        
        return response.model_dump()

    def update_context(
        self,
        goal: Optional[str] = None,
        why: Optional[str] = None,
        agent_id: Optional[str] = None,
        agent_status: Optional[str] = None,
    ) -> dict[str, Any]:
        """Update the execution context.
        
        Args:
            goal: New execution goal
            why: New decision rationale
            agent_id: Agent identifier (required if updating agent_status)
            agent_status: New status for the agent (natural language)
            
        Returns:
            Updated context as dict
        """
        context = self.storage.load_context()
        
        if goal is not None:
            context.goal = goal
        
        if why is not None:
            context.why = why
        
        if agent_id is not None and agent_status is not None:
            context.agents[agent_id] = agent_status
        
        self.storage.save_context(context)
        
        return {
            "goal": context.goal,
            "why": context.why,
            "agents": context.agents,
        }

    # === Task Tools ===

    def list_tasks(
        self,
        task_id: Optional[str] = None,
        status: Optional[str] = None,
        assignee: Optional[str] = None,
        include_archived: bool = False,
    ) -> dict[str, Any] | list[dict[str, Any]]:
        """List or get tasks.
        
        Args:
            task_id: Get a specific task by ID
            status: Filter by status (planned/active/done)
            assignee: Filter by assignee
            include_archived: Include archived tasks
            
        Returns:
            Single task dict if task_id specified, else list of task dicts
        """
        if task_id:
            task = self.storage.load_task(task_id, include_archived=include_archived)
            if task is None:
                raise ValueError(f"Task not found: {task_id}")
            return self._task_to_dict(task)
        
        tasks = self.storage.list_tasks(include_archived=include_archived)
        
        # Apply filters
        if status:
            try:
                status_enum = TaskStatus(status)
                tasks = [t for t in tasks if t.status == status_enum]
            except ValueError:
                raise ValueError(f"Invalid status: {status}")
        
        if assignee:
            tasks = [t for t in tasks if t.assignee == assignee]
        
        return [self._task_to_dict(t) for t in tasks]

    def create_task(
        self,
        name: str,
        goal: str,
        steps: list[dict[str, str]],
        task_id: Optional[str] = None,
        assignee: Optional[str] = None,
    ) -> dict[str, Any]:
        """Create a new task.
        
        Args:
            name: Task name
            goal: Completion criteria
            steps: List of step dicts with 'name' and 'done' keys
            task_id: Optional custom task ID (auto-generated if not provided)
            assignee: Optional assignee
            
        Returns:
            Created task as dict
        """
        # Generate ID if not provided
        if task_id is None:
            task_id = self.storage.generate_task_id()
        
        # Validate task_id format
        if not task_id.startswith("T") or not task_id[1:].isdigit():
            raise ValueError(f"Invalid task ID format: {task_id}")
        
        # Check if task already exists
        if self.storage.load_task(task_id) is not None:
            raise ValueError(f"Task already exists: {task_id}")
        
        # Create steps
        task_steps = []
        for i, s in enumerate(steps, start=1):
            step = Step(
                id=f"S{i}",
                name=s.get("name", ""),
                done=s.get("done", ""),
                status=StepStatus.PENDING,
            )
            task_steps.append(step)
        
        # Create task
        task = Task(
            id=task_id,
            name=name,
            goal=goal,
            status=TaskStatus.PLANNED,
            assignee=assignee,
            created_at=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
            steps=task_steps,
        )
        
        self.storage.save_task(task)
        
        return self._task_to_dict(task)

    def update_task(
        self,
        task_id: str,
        status: Optional[str] = None,
        name: Optional[str] = None,
        goal: Optional[str] = None,
        assignee: Optional[str] = None,
        step_id: Optional[str] = None,
        step_status: Optional[str] = None,
        steps: Optional[list[dict[str, Any]]] = None,
    ) -> dict[str, Any]:
        """Update a task.
        
        Args:
            task_id: Task ID to update
            status: New task status
            name: New task name
            goal: New completion criteria
            assignee: New assignee
            step_id: Step ID to update (use with step_status)
            step_status: New status for the step
            steps: Replace all steps (list of step dicts)
            
        Returns:
            Updated task as dict
        """
        task = self.storage.load_task(task_id)
        if task is None:
            raise ValueError(f"Task not found: {task_id}")
        
        if status is not None:
            try:
                task.status = TaskStatus(status)
            except ValueError:
                raise ValueError(f"Invalid status: {status}")
        
        if name is not None:
            task.name = name
        
        if goal is not None:
            task.goal = goal
        
        if assignee is not None:
            task.assignee = assignee
        
        # Update specific step
        if step_id is not None and step_status is not None:
            found = False
            for step in task.steps:
                if step.id == step_id:
                    try:
                        step.status = StepStatus(step_status)
                    except ValueError:
                        raise ValueError(f"Invalid step status: {step_status}")
                    found = True
                    break
            if not found:
                raise ValueError(f"Step not found: {step_id}")
        
        # Replace all steps
        if steps is not None:
            new_steps = []
            for s in steps:
                step = Step(
                    id=s.get("id", ""),
                    name=s.get("name", ""),
                    done=s.get("done", ""),
                    status=StepStatus(s.get("status", "pending")),
                )
                new_steps.append(step)
            task.steps = new_steps
        
        self.storage.save_task(task)
        
        return self._task_to_dict(task)

    def delete_task(self, task_id: str) -> dict[str, Any]:
        """Delete a task.
        
        Args:
            task_id: Task ID to delete
            
        Returns:
            Success status
        """
        if not self.storage.delete_task(task_id):
            raise ValueError(f"Task not found: {task_id}")
        
        return {"deleted": True, "task_id": task_id}

    # === Entry Tools (Notes and References) ===

    def add_entry(
        self,
        type: str,
        content: str,
        uri: Optional[str] = None,
        score: int = 15,
    ) -> dict[str, Any]:
        """Add a note or reference entry.
        
        Args:
            type: Entry type ("note" or "reference")
            content: Note content or reference description
            uri: File path/URI (required for references)
            score: Initial score (10-100, default 15)
            
        Returns:
            Created entry as dict
        """
        # Validate type
        try:
            entry_type = EntryType(type)
        except ValueError:
            raise ValueError(f"Invalid entry type: {type}. Must be 'note' or 'reference'.")
        
        # Validate score
        if score < 10 or score > 100:
            raise ValueError(f"Score must be between 10 and 100, got: {score}")
        
        context = self.storage.load_context()
        
        if entry_type == EntryType.NOTE:
            entry_id = self.storage.generate_note_id(context)
            note = Note(id=entry_id, score=score, content=content)
            context.notes.append(note)
            result = {"id": entry_id, "type": "note", "score": score, "content": content}
        else:
            if uri is None:
                raise ValueError("URI is required for reference entries")
            entry_id = self.storage.generate_reference_id(context)
            ref = Reference(id=entry_id, score=score, uri=uri, note=content)
            context.references.append(ref)
            result = {"id": entry_id, "type": "reference", "score": score, "uri": uri, "note": content}
        
        self.storage.save_context(context)
        
        return result

    def update_entry(
        self,
        id: str,
        content: Optional[str] = None,
        score: Optional[int] = None,
    ) -> dict[str, Any]:
        """Update a note or reference entry.
        
        Args:
            id: Entry ID (N001 or R001)
            content: New content
            score: New score (to renew/extend)
            
        Returns:
            Updated entry as dict
        """
        context = self.storage.load_context()
        
        # Check if it's a note
        if id.startswith("N"):
            for note in context.notes:
                if note.id == id:
                    if content is not None:
                        note.content = content
                    if score is not None:
                        note.score = score
                    self.storage.save_context(context)
                    return {"id": id, "type": "note", "score": note.score, "content": note.content}
            raise ValueError(f"Note not found: {id}")
        
        # Check if it's a reference
        if id.startswith("R"):
            for ref in context.references:
                if ref.id == id:
                    if content is not None:
                        ref.note = content
                    if score is not None:
                        ref.score = score
                    self.storage.save_context(context)
                    return {"id": id, "type": "reference", "score": ref.score, "uri": ref.uri, "note": ref.note}
            raise ValueError(f"Reference not found: {id}")
        
        raise ValueError(f"Invalid entry ID format: {id}")

    def remove_entry(self, id: str) -> dict[str, Any]:
        """Remove a note or reference entry.
        
        Args:
            id: Entry ID (N001 or R001)
            
        Returns:
            Success status
        """
        context = self.storage.load_context()
        
        # Check if it's a note
        if id.startswith("N"):
            for i, note in enumerate(context.notes):
                if note.id == id:
                    del context.notes[i]
                    self.storage.save_context(context)
                    return {"deleted": True, "id": id}
            raise ValueError(f"Note not found: {id}")
        
        # Check if it's a reference
        if id.startswith("R"):
            for i, ref in enumerate(context.references):
                if ref.id == id:
                    del context.references[i]
                    self.storage.save_context(context)
                    return {"deleted": True, "id": id}
            raise ValueError(f"Reference not found: {id}")
        
        raise ValueError(f"Invalid entry ID format: {id}")

    # === Helpers ===

    def _task_to_dict(self, task: Task) -> dict[str, Any]:
        """Convert a Task to a dict with computed fields."""
        current_step = task.current_step
        return {
            "id": task.id,
            "name": task.name,
            "goal": task.goal,
            "status": task.status.value,
            "assignee": task.assignee,
            "created_at": task.created_at,
            "steps": [
                {
                    "id": s.id,
                    "name": s.name,
                    "done": s.done,
                    "status": s.status.value,
                }
                for s in task.steps
            ],
            "current_step": current_step.id if current_step else None,
            "progress": task.progress,
        }
