"""ACPX integration — connect AI coding agents via Agent Client Protocol.

acpx provides a unified CLI interface to Claude Code, Codex, Gemini, Qwen,
Cursor, Copilot, and other ACP-compatible agents.

Install: ``npm install -g acpx@latest``

Usage::

    from logos_adk.tools.acpx import acpx_tool, acpx_agent_tools

    # Single agent as tool
    claude = acpx_tool("claude")
    codex = acpx_tool("codex")

    # All available agents as tools
    tools = acpx_agent_tools(["claude", "codex", "gemini", "qwen"])

    agent = Agent("orchestrator").tools(tools)
"""
from __future__ import annotations

import asyncio
import shutil

from logos_adk.tools import Tool


def _find_acpx() -> str:
    """Find the acpx binary, preferring global install over npx."""
    path = shutil.which("acpx")
    if path:
        return path
    # Fall back to npx
    npx = shutil.which("npx")
    if npx:
        return f"{npx} acpx@latest"
    raise FileNotFoundError(
        "acpx not found. Install with: npm install -g acpx@latest"
    )


def acpx_tool(
    agent: str,
    *,
    name: str | None = None,
    description: str | None = None,
    session: str | None = None,
    approve_all: bool = True,
    format: str = "text",
    timeout: float = 300.0,
    cwd: str | None = None,
    one_shot: bool = False,
) -> Tool:
    """Create a Tool that delegates to an ACP agent via acpx.

    Args:
        agent: Agent name — "claude", "codex", "gemini", "qwen", "cursor",
               "copilot", "droid", "kimi", etc.
        name: Tool name (defaults to agent name).
        description: Tool description.
        session: Named session (for persistent multi-turn context).
        approve_all: Auto-approve all actions (default True for agent use).
        format: Output format — "text", "json", "quiet".
        timeout: Max seconds to wait.
        cwd: Working directory for the agent.
        one_shot: If True, use ``exec`` mode (no session).

    Usage::

        claude = acpx_tool("claude", description="Solve complex coding tasks with Claude")
        codex = acpx_tool("codex", session="backend", cwd="/path/to/repo")
        gemini = acpx_tool("gemini", one_shot=True)

        agent = Agent("boss").tools([claude, codex, gemini])
    """
    tool_name = name or agent
    tool_desc = description or f"Delegate a task to {agent} via ACP"
    acpx_agent = agent

    _acpx_bin: str | None = None

    async def _run_acpx(prompt: str) -> str:
        nonlocal _acpx_bin
        proc = None
        if _acpx_bin is None:
            _acpx_bin = _find_acpx()

        cmd_parts = []
        if " " in _acpx_bin:
            # npx fallback — split into parts
            cmd_parts.extend(_acpx_bin.split())
        else:
            cmd_parts.append(_acpx_bin)

        if approve_all:
            cmd_parts.append("--approve-all")
        if format != "text":
            cmd_parts.extend(["--format", format])
        if cwd:
            cmd_parts.extend(["--cwd", cwd])

        cmd_parts.append(acpx_agent)

        if one_shot:
            cmd_parts.append("exec")
        elif session:
            cmd_parts.extend(["-s", session])

        cmd_parts.append(prompt)

        try:
            proc = await asyncio.create_subprocess_exec(
                *cmd_parts,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=timeout,
            )
            output = stdout.decode().strip()
            if proc.returncode != 0 and not output:
                err = stderr.decode().strip()
                return f"Error (exit {proc.returncode}): {err}"
            return output
        except asyncio.TimeoutError:
            if proc is not None and proc.returncode is None:
                proc.kill()
                await proc.wait()
            return f"Error: {acpx_agent} timed out after {timeout}s"
        except FileNotFoundError:
            return "Error: acpx not found. Install with: npm install -g acpx@latest"

    return Tool(fn=_run_acpx, name=tool_name, description=tool_desc)


# Pre-configured agent descriptions for better LLM tool selection
_AGENT_DESCRIPTIONS: dict[str, str] = {
    "claude": "Delegate a coding task to Claude Code — best for complex reasoning and architecture",
    "codex": "Delegate a coding task to OpenAI Codex — fast for straightforward code generation",
    "gemini": "Delegate a coding task to Gemini — good for research and broad knowledge tasks",
    "qwen": "Delegate a coding task to Qwen Code — strong at multilingual code generation",
    "cursor": "Delegate a coding task to Cursor — integrated IDE-style coding",
    "copilot": "Delegate a coding task to GitHub Copilot — great for code completion and suggestions",
    "kimi": "Delegate a coding task to Kimi — long context window for large codebases",
}


def acpx_agent_tools(
    agents: list[str] | None = None,
    *,
    session: str | None = None,
    approve_all: bool = True,
    timeout: float = 300.0,
    cwd: str | None = None,
) -> list[Tool]:
    """Create tools for multiple ACP agents at once.

    Args:
        agents: List of agent names. Defaults to ["claude", "codex", "gemini", "qwen"].
        session: Shared session name (each agent gets its own session).
        approve_all: Auto-approve actions.
        timeout: Max seconds per agent call.
        cwd: Working directory.

    Usage::

        tools = acpx_agent_tools(["claude", "codex", "gemini"])
        agent = Agent("orchestrator").tools(tools)
    """
    if agents is None:
        agents = ["claude", "codex", "gemini", "qwen"]

    return [
        acpx_tool(
            agent_name,
            description=_AGENT_DESCRIPTIONS.get(agent_name),
            session=session,
            approve_all=approve_all,
            timeout=timeout,
            cwd=cwd,
        )
        for agent_name in agents
    ]
