"""Unified agent tool implementation.

This module provides the AgentTool for delegating tasks to sub-agents,
supporting both one-off and long-running RPC modes, including A2A communication.
"""

import re
import json
import time
import uuid
import asyncio

# Import litellm with warnings suppressed
import warnings
from typing import (
    Any,
    Dict,
    List,
    Unpack,
    Optional,
    Annotated,
    TypedDict,
    final,
    override,
)

with warnings.catch_warnings():
    warnings.simplefilter("ignore", DeprecationWarning)
from pydantic import Field
from openai.types.chat import ChatCompletionMessageParam
from mcp.server.fastmcp import Context as MCPContext

from hanzo_mcp.tools.jupyter import get_read_only_jupyter_tools
from hanzo_mcp.tools.filesystem import get_read_only_filesystem_tools
from hanzo_mcp.tools.common.base import BaseTool
from hanzo_mcp.tools.agent.prompt import (
    get_default_model,
    get_system_prompt,
    get_allowed_agent_tools,
)
from hanzo_mcp.tools.common.context import (
    ToolContext,
    create_tool_context,
)
from hanzo_mcp.tools.common.batch_tool import BatchTool
from hanzo_mcp.tools.common.permissions import PermissionManager
from hanzo_mcp.tools.common.auto_timeout import auto_timeout

# Parameter types
Action = Annotated[
    str,
    Field(
        description="Action: run (default), start, call, stop, list",
        default="run",
    ),
]

Prompts = Annotated[
    Optional[str | List[str]],
    Field(
        description="Task(s) for agent (must include absolute paths starting with /)",
        default=None,
    ),
]

Mode = Annotated[
    str,
    Field(
        description="Execution mode: oneoff (default) or rpc",
        default="oneoff",
    ),
]

AgentId = Annotated[
    Optional[str],
    Field(
        description="Agent ID for RPC mode",
        default=None,
    ),
]

Method = Annotated[
    Optional[str],
    Field(
        description="Method to call on RPC agent",
        default=None,
    ),
]

Args = Annotated[
    Optional[Dict[str, Any]],
    Field(
        description="Arguments for RPC method call",
        default=None,
    ),
]

Model = Annotated[
    Optional[str],
    Field(
        description="Model to use (e.g., lm-studio/local-model, openai/gpt-4o)",
        default=None,
    ),
]


class AgentParams(TypedDict, total=False):
    """Parameters for agent tool."""

    action: str
    prompts: Optional[str | List[str]]
    mode: str
    agent_id: Optional[str]
    method: Optional[str]
    args: Optional[Dict[str, Any]]
    model: Optional[str]


class RPCAgent:
    """Long-running RPC agent."""

    def __init__(self, agent_id: str, model: str, system_prompt: str, tools: List[BaseTool]):
        self.agent_id = agent_id
        self.model = model
        self.system_prompt = system_prompt
        self.tools = tools
        self.messages: List[ChatCompletionMessageParam] = [{"role": "system", "content": system_prompt}]
        self.created_at = time.time()
        self.last_used = time.time()
        self.call_count = 0

    async def call_method(self, method: str, args: Dict[str, Any], tool_ctx: ToolContext) -> str:
        """Call a method on the RPC agent."""
        self.last_used = time.time()
        self.call_count += 1

        # Build prompt based on method
        if method == "search":
            prompt = f"Search for: {args.get('query', 'unknown')}"
        elif method == "analyze":
            prompt = f"Analyze: {args.get('target', 'unknown')}"
        elif method == "execute":
            prompt = f"Execute: {args.get('command', 'unknown')}"
        else:
            # Generic method call
            prompt = f"Method: {method}, Args: {json.dumps(args)}"

        # Add to conversation
        self.messages.append({"role": "user", "content": prompt})

        # Get response
        # (simplified - would integrate with full agent execution logic)
        response = f"Executed {method} with args {args}"
        self.messages.append({"role": "assistant", "content": response})

        return response


@final
class AgentTool(BaseTool):
    """Unified agent tool with one-off and RPC modes."""

    def __init__(
        self,
        permission_manager: PermissionManager,
        model: str | None = None,
        api_key: str | None = None,
        base_url: str | None = None,
        max_tokens: int | None = None,
        max_iterations: int = 10,
        max_tool_uses: int = 30,
    ):
        """Initialize the agent tool."""
        self.permission_manager = permission_manager
        self.model_override = model
        self.api_key_override = api_key
        self.base_url_override = base_url
        self.max_tokens_override = max_tokens
        self.max_iterations = max_iterations
        self.max_tool_uses = max_tool_uses

        # RPC agent registry
        self._rpc_agents: Dict[str, RPCAgent] = {}

        # Available tools
        self.available_tools: list[BaseTool] = []
        self.available_tools.extend(get_read_only_filesystem_tools(self.permission_manager))
        self.available_tools.extend(get_read_only_jupyter_tools(self.permission_manager))
        self.available_tools.append(BatchTool({t.name: t for t in self.available_tools}))

    @property
    @override
    def name(self) -> str:
        """Get the tool name."""
        return "agent"

    @property
    @override
    def description(self) -> str:
        """Get the tool description."""
        tools = [t.name for t in self.available_tools]

        return f"""AI agents with tools: {", ".join(tools)}. Actions: run (default), start, call, stop, list.

Usage:
agent "Search for config files in /project"
agent --action start --mode rpc --model lm-studio/local-model
agent --action call --agent-id abc123 --method search --args '{{"query": "database"}}'
agent --action list

Modes:
- oneoff: Single task execution (default)
- rpc: Long-running agent for multiple calls (A2A support)"""

    @override
    @auto_timeout("agent")
    async def call(
        self,
        ctx: MCPContext,
        **params: Unpack[AgentParams],
    ) -> str:
        """Execute agent operation."""
        tool_ctx = create_tool_context(ctx)
        await tool_ctx.set_tool_info(self.name)

        # Extract action
        action = params.get("action", "run")

        # Route to appropriate handler
        if action == "run":
            return await self._handle_run(params, tool_ctx)
        elif action == "start":
            return await self._handle_start(params, tool_ctx)
        elif action == "call":
            return await self._handle_call(params, tool_ctx)
        elif action == "stop":
            return await self._handle_stop(params, tool_ctx)
        elif action == "list":
            return await self._handle_list(tool_ctx)
        else:
            return f"Error: Unknown action '{action}'. Valid actions: run, start, call, stop, list"

    async def _handle_run(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
        """Handle one-off agent run (default action)."""
        prompts = params.get("prompts")
        if not prompts:
            return "Error: prompts required for run action"

        # Convert to list
        if isinstance(prompts, str):
            prompt_list = [prompts]
        else:
            prompt_list = prompts

        # Validate prompts
        for prompt in prompt_list:
            if not self._validate_prompt(prompt):
                return f"Error: Prompt must contain absolute paths starting with /: {prompt[:50]}..."

        # Execute agents
        start_time = time.time()

        if len(prompt_list) == 1:
            await tool_ctx.info("Launching agent")
            result = await self._execute_agent(prompt_list[0], params.get("model"), tool_ctx)
        else:
            await tool_ctx.info(f"Launching {len(prompt_list)} agents in parallel")
            result = await self._execute_multiple_agents(prompt_list, params.get("model"), tool_ctx)

        execution_time = time.time() - start_time

        return f"""Agent execution completed in {execution_time:.2f} seconds.

AGENT RESPONSE:
{result}"""

    async def _handle_start(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
        """Start a new RPC agent."""
        mode = params.get("mode", "oneoff")
        if mode != "rpc":
            return "Error: start action only valid for rpc mode"

        # Generate agent ID
        agent_id = str(uuid.uuid4())[:8]

        # Get model
        model = params.get("model") or get_default_model(self.model_override)

        # Get available tools
        agent_tools = get_allowed_agent_tools(
            self.available_tools,
            self.permission_manager,
        )

        # Create system prompt
        system_prompt = get_system_prompt(
            agent_tools,
            self.permission_manager,
        )

        # Create RPC agent
        agent = RPCAgent(agent_id, model, system_prompt, agent_tools)
        self._rpc_agents[agent_id] = agent

        await tool_ctx.info(f"Started RPC agent {agent_id} with model {model}")

        return f"""Started RPC agent:
- ID: {agent_id}
- Model: {model}
- Tools: {len(agent_tools)}

Use 'agent --action call --agent-id {agent_id} --method <method> --args <args>' to interact."""

    async def _handle_call(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
        """Call method on RPC agent."""
        agent_id = params.get("agent_id")
        if not agent_id:
            return "Error: agent_id required for call action"

        if agent_id not in self._rpc_agents:
            return f"Error: Agent {agent_id} not found. Use 'agent --action list' to see active agents."

        method = params.get("method")
        if not method:
            return "Error: method required for call action"

        args = params.get("args", {})

        # Call agent method
        agent = self._rpc_agents[agent_id]
        await tool_ctx.info(f"Calling {method} on agent {agent_id}")

        try:
            result = await agent.call_method(method, args, tool_ctx)
            return f"Agent {agent_id} response:\n{result}"
        except Exception as e:
            await tool_ctx.error(f"Error calling agent: {str(e)}")
            return f"Error calling agent: {str(e)}"

    async def _handle_stop(self, params: Dict[str, Any], tool_ctx: ToolContext) -> str:
        """Stop an RPC agent."""
        agent_id = params.get("agent_id")
        if not agent_id:
            return "Error: agent_id required for stop action"

        if agent_id not in self._rpc_agents:
            return f"Error: Agent {agent_id} not found"

        agent = self._rpc_agents.pop(agent_id)
        await tool_ctx.info(f"Stopped agent {agent_id}")

        return f"""Stopped agent {agent_id}:
- Runtime: {time.time() - agent.created_at:.2f} seconds
- Calls: {agent.call_count}"""

    async def _handle_list(self, tool_ctx: ToolContext) -> str:
        """List active RPC agents."""
        if not self._rpc_agents:
            return "No active RPC agents"

        output = ["=== Active RPC Agents ==="]
        for agent_id, agent in self._rpc_agents.items():
            runtime = time.time() - agent.created_at
            idle = time.time() - agent.last_used
            output.append(f"\nAgent {agent_id}:")
            output.append(f"  Model: {agent.model}")
            output.append(f"  Runtime: {runtime:.2f}s")
            output.append(f"  Idle: {idle:.2f}s")
            output.append(f"  Calls: {agent.call_count}")

        return "\n".join(output)

    def _validate_prompt(self, prompt: str) -> bool:
        """Validate that prompt contains absolute paths."""
        absolute_path_pattern = r"/(?:[^/\s]+/)*[^/\s]+"
        return bool(re.search(absolute_path_pattern, prompt))

    async def _execute_agent(self, prompt: str, model: Optional[str], tool_ctx: ToolContext) -> str:
        """Execute a single agent (simplified - would use full logic from agent_tool.py)."""
        # This would integrate the full agent execution logic from agent_tool.py
        # For now, return a placeholder
        return f"Executed agent with prompt: {prompt[:100]}..."

    async def _execute_multiple_agents(self, prompts: List[str], model: Optional[str], tool_ctx: ToolContext) -> str:
        """Execute multiple agents in parallel."""
        tasks = []
        for prompt in prompts:
            task = self._execute_agent(prompt, model, tool_ctx)
            tasks.append(task)

        results = await asyncio.gather(*tasks, return_exceptions=True)

        formatted_results = []
        for i, result in enumerate(results):
            if isinstance(result, Exception):
                formatted_results.append(f"Agent {i + 1} Error:\n{str(result)}")
            else:
                formatted_results.append(f"Agent {i + 1} Result:\n{result}")

        return "\n\n---\n\n".join(formatted_results)

    def register(self, mcp_server) -> None:
        """Register this tool with the MCP server."""
        pass
