# 中文注释：
# 文件：echobot/agent/tools/call_agent.py
# 说明：Agent 间调用工具。

"""Call Agent Tool - Agent 间调用"""

import re
from typing import Any

from echobot.agent.tools.base import Tool


class CallAgentTool(Tool):
    """
    Agent 间调用工具

    允许当前 Agent 调用其他 Agent 来完成任务。
    """

    def __init__(
        self,
        call_agent_callback,
        get_available_agents_callback,
        current_agent_id: str,
    ):
        """
        Initialize CallAgentTool.

        Args:
            call_agent_callback: 调用 Agent 的回调函数 (caller_id, target_id, task, context) -> str
            get_available_agents_callback: 获取可用 Agent 列表的回调
            current_agent_id: 当前 Agent 的 ID
        """
        self._call_agent = call_agent_callback
        self._get_available_agents = get_available_agents_callback
        self._current_agent_id = current_agent_id

    @property
    def name(self) -> str:
        return "call_agent"

    @property
    def description(self) -> str:
        return "Call another agent to help with the task. Use this when you need assistance from a specialized agent."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {
                "agent_id": {
                    "type": "string",
                    "description": "Target agent ID/name/role to call. Prefer exact ID from list_agents.",
                },
                "task": {
                    "type": "string",
                    "description": "Task description for the target agent to execute",
                },
                "context": {
                    "type": "object",
                    "description": "Optional context information to pass to the target agent",
                },
            },
            "required": ["agent_id", "task"],
        }

    async def execute(self, agent_id: str, task: str, context: dict | None = None, **kwargs) -> str:
        """Execute agent-to-agent call."""
        if self._call_agent is None:
            return "Error: Agent collaboration is not configured"

        try:
            resolved_agent_id = self._resolve_agent_id(agent_id)

            result = await self._call_agent(
                caller_id=self._current_agent_id,
                target_agent_id=resolved_agent_id,
                task=task,
                context=context,
            )
            return result
        except Exception as e:
            return f"Error calling agent '{agent_id}': {str(e)}"

    def _resolve_agent_id(self, raw_agent_id: str) -> str:
        """Resolve a user-provided target to an available concrete agent id."""
        resolved_agent_id = raw_agent_id.strip()
        if not self._get_available_agents:
            return resolved_agent_id

        available = self._get_available_agents() or []
        available_ids = [str(agent.get("id", "")).strip() for agent in available if agent.get("id")]
        available_set = set(available_ids)
        if resolved_agent_id in available_set:
            return resolved_agent_id

        normalized = resolved_agent_id.lower()

        # 1) Case-insensitive exact match on id/name/role.
        for agent in available:
            agent_id = str(agent.get("id", "")).strip()
            name = str(agent.get("name", "")).strip()
            role = str(agent.get("role", "")).strip()
            if normalized in {agent_id.lower(), name.lower(), role.lower()}:
                return agent_id

        # 2) Generic fuzzy match across available metadata (no hardcoded role map).
        query_tokens = self._tokenize(normalized)
        best_id = None
        best_score = 0
        for agent in available:
            agent_id = str(agent.get("id", "")).strip()
            fields = [
                agent_id.lower(),
                str(agent.get("name", "")).lower(),
                str(agent.get("role", "")).lower(),
                str(agent.get("description", "")).lower(),
            ]

            score = 0
            for field in fields:
                if not field:
                    continue
                if normalized in field or field in normalized:
                    score += 4
                overlap = query_tokens & self._tokenize(field)
                score += len(overlap)

            if score > best_score:
                best_score = score
                best_id = agent_id

        if best_id and best_score > 0:
            return best_id

        # 3) If only one callable agent exists, default to it.
        if len(available_ids) == 1:
            return available_ids[0]

        return resolved_agent_id

    def _tokenize(self, text: str) -> set[str]:
        """
        Tokenize text for lightweight matching.
        - English words/numbers/underscore as a token
        - Chinese continuous chars as one token + per-char tokens
        """
        tokens: set[str] = set()
        if not text:
            return tokens

        parts = re.findall(r"[a-z0-9_]+|[\u4e00-\u9fff]+", text.lower())
        for part in parts:
            tokens.add(part)
            # Add per-character tokens for CJK to improve partial matching.
            if re.search(r"[\u4e00-\u9fff]", part) and len(part) > 1:
                tokens.update(list(part))
        return tokens


class ListAgentsTool(Tool):
    """
    列出可用 Agent 工具

    列出当前可以调用的其他 Agent。
    """

    def __init__(self, get_available_agents_callback):
        """
        Initialize ListAgentsTool.

        Args:
            get_available_agents_callback: 获取可用 Agent 列表的回调
        """
        self._get_available_agents = get_available_agents_callback

    @property
    def name(self) -> str:
        return "list_agents"

    @property
    def description(self) -> str:
        return "List all available agents that can be called for assistance."

    @property
    def parameters(self) -> dict[str, Any]:
        return {
            "type": "object",
            "properties": {},
        }

    async def execute(self, **kwargs) -> str:
        """Execute list agents."""
        if self._get_available_agents is None:
            return "Error: Agent collaboration is not configured"

        try:
            agents = self._get_available_agents()
            if not agents:
                return "No available agents to call."

            lines = ["Available agents:"]
            for agent in agents:
                desc = str(agent.get("description", "")).strip()
                if desc:
                    lines.append(
                        f"- {agent['id']}: {agent['name']} (role: {agent['role']}) - {desc}"
                    )
                else:
                    lines.append(f"- {agent['id']}: {agent['name']} (role: {agent['role']})")

            return "\n".join(lines)
        except Exception as e:
            return f"Error listing agents: {str(e)}"
