"""Tool adapters — launch and interact with AI dev tools.

Each adapter knows how to:
  1. Build CLI arguments from a prompt + config
  2. Launch the tool process
  3. Stream/capture output
  4. Report completion status
"""

from __future__ import annotations

import asyncio
import shutil
import subprocess
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

import structlog

from proxym.tools import ToolDefinition, ToolRegistry
from proxym.tools.prompt_builder import build_prompt

logger = structlog.get_logger()


@dataclass
class ToolResult:
    """Result of a tool execution."""
    tool: str
    ticket_id: str
    exit_code: int = -1
    stdout: str = ""
    stderr: str = ""
    duration_s: float = 0
    files_changed: list[str] = field(default_factory=list)

    @property
    def success(self) -> bool:
        return self.exit_code == 0

    def summary(self) -> dict[str, Any]:
        return {
            "tool": self.tool,
            "ticket_id": self.ticket_id,
            "success": self.success,
            "exit_code": self.exit_code,
            "duration_s": round(self.duration_s, 2),
            "files_changed": self.files_changed,
            "stdout_lines": len(self.stdout.splitlines()),
            "stderr_lines": len(self.stderr.splitlines()),
        }


class BaseAdapter(ABC):
    """Base class for tool adapters."""

    def __init__(self, tool: ToolDefinition) -> None:
        self.tool = tool

    @abstractmethod
    def build_args(
        self, prompt: str, project_dir: str, mode: str = "", model: str = "",
        files: list[str] | None = None,
    ) -> list[str]:
        """Build CLI argument list."""
        ...

    async def run(
        self,
        prompt: str,
        project_dir: str,
        ticket_id: str = "",
        mode: str = "",
        model: str = "",
        files: list[str] | None = None,
        timeout: float = 300,
    ) -> ToolResult:
        """Launch the tool and wait for completion."""
        import time
        args = self.build_args(prompt, project_dir, mode, model, files)
        binary = shutil.which(self.tool.binary)
        if not binary:
            return ToolResult(
                tool=self.tool.name, ticket_id=ticket_id,
                exit_code=127, stderr=f"{self.tool.binary} not found on PATH",
            )

        logger.info("tool_adapter_run", tool=self.tool.name, args=args[:3], cwd=project_dir)
        t0 = time.monotonic()
        try:
            proc = await asyncio.create_subprocess_exec(
                *args,
                cwd=project_dir,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE,
            )
            stdout_b, stderr_b = await asyncio.wait_for(
                proc.communicate(), timeout=timeout,
            )
            duration = time.monotonic() - t0
            result = ToolResult(
                tool=self.tool.name,
                ticket_id=ticket_id,
                exit_code=proc.returncode or 0,
                stdout=stdout_b.decode(errors="replace"),
                stderr=stderr_b.decode(errors="replace"),
                duration_s=duration,
            )
            logger.info("tool_adapter_done", tool=self.tool.name,
                        exit_code=result.exit_code, duration=f"{duration:.1f}s")
            return result
        except asyncio.TimeoutError:
            duration = time.monotonic() - t0
            return ToolResult(
                tool=self.tool.name, ticket_id=ticket_id,
                exit_code=-1, stderr=f"Timeout after {timeout}s",
                duration_s=duration,
            )
        except Exception as exc:
            duration = time.monotonic() - t0
            return ToolResult(
                tool=self.tool.name, ticket_id=ticket_id,
                exit_code=-1, stderr=str(exc), duration_s=duration,
            )


class AiderAdapter(BaseAdapter):
    """Adapter for aider CLI."""

    def build_args(
        self, prompt: str, project_dir: str, mode: str = "", model: str = "",
        files: list[str] | None = None,
    ) -> list[str]:
        args = ["aider", "--yes-always", "--no-auto-commits"]
        if mode == "architect":
            args.append("--architect")
        if model:
            args.extend(["--model", model])
        if files:
            for f in files:
                args.append(f)
        args.extend(["--message", prompt])
        return args


class ClaudeCodeAdapter(BaseAdapter):
    """Adapter for claude CLI (Anthropic Claude Code)."""

    def build_args(
        self, prompt: str, project_dir: str, mode: str = "", model: str = "",
        files: list[str] | None = None,
    ) -> list[str]:
        args = ["claude", "--print", "--dangerously-skip-permissions"]
        if model:
            args.extend(["--model", model])
        args.append(prompt)
        return args


# ── Adapter registry ─────────────────────────────────────

_ADAPTER_MAP: dict[str, type[BaseAdapter]] = {
    "aider": AiderAdapter,
    "claude-code": ClaudeCodeAdapter,
}


def get_adapter(tool_name: str) -> BaseAdapter | None:
    """Return an adapter instance for the named tool, or None."""
    registry = ToolRegistry.get()
    tool = registry.get_tool(tool_name)
    if not tool:
        return None
    adapter_cls = _ADAPTER_MAP.get(tool_name)
    if not adapter_cls:
        return None
    return adapter_cls(tool)


def register_adapter(tool_name: str, adapter_cls: type[BaseAdapter]) -> None:
    """Register a custom adapter class for a tool."""
    _ADAPTER_MAP[tool_name] = adapter_cls
