"""Tool registry — manage AI development tools (aider, claude-code, windsurf, etc.).

Each tool is described by a ToolDefinition and can be discovered, configured,
and dispatched to via the ToolRegistry singleton.
"""

from __future__ import annotations

import copy
import os
import shutil
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Any

import structlog

logger = structlog.get_logger()


class ToolCategory(str, Enum):
    """High-level tool categories."""
    IDE = "ide"
    AGENT_CLI = "agent_cli"
    BROWSER = "browser"
    CUSTOM = "custom"


class ToolCapability(str, Enum):
    """Capabilities a tool can advertise."""
    CODE_EDIT = "code_edit"
    CODE_REVIEW = "code_review"
    TEST_RUN = "test_run"
    CHAT = "chat"
    FILE_CREATE = "file_create"
    REFACTOR = "refactor"
    DEBUG = "debug"
    ARCHITECT = "architect"


@dataclass
class ToolInstance:
    """Concrete runtime instance for a registered tool."""
    name: str
    kind: str
    command: list[str] = field(default_factory=list)
    service: str = ""
    profile: str = ""
    container_name: str = ""
    description: str = ""
    notes: str = ""
    force_available: bool | None = None

    @property
    def is_available(self) -> bool:
        if self.force_available is not None:
            return self.force_available
        if self.kind == "docker":
            return shutil.which("docker") is not None
        if self.command:
            return shutil.which(self.command[0]) is not None
        return False

    def summary(self) -> dict[str, Any]:
        return {
            "name": self.name,
            "kind": self.kind,
            "command": self.command,
            "service": self.service,
            "profile": self.profile,
            "container_name": self.container_name,
            "description": self.description,
            "notes": self.notes,
            "available": self.is_available,
        }


@dataclass
class ToolDefinition:
    """Describes a registered development tool."""
    name: str
    category: ToolCategory
    binary: str = ""                                # e.g. "aider", "claude"
    capabilities: list[ToolCapability] = field(default_factory=list)
    default_model: str = ""                         # alias like "smart" or full id
    modes: list[str] = field(default_factory=list)  # e.g. ["code", "architect", "ask"]
    config: dict[str, Any] = field(default_factory=dict)
    instances: list[ToolInstance] = field(default_factory=list)
    default_instance: str = "host"
    description: str = ""
    force_available: bool | None = None

    @property
    def is_available(self) -> bool:
        """Check tool availability.

        Uses *force_available* override first (set via PROXYM_TOOLS_AVAILABLE
        env var for containerised deployments), then falls back to binary
        detection on PATH and docker-backed instances.
        """
        if self.force_available is not None:
            return self.force_available
        if self.binary and shutil.which(self.binary) is not None:
            return True
        return any(instance.is_available for instance in self.instances)

    def summary(self) -> dict[str, Any]:
        return {
            "name": self.name,
            "category": self.category.value,
            "binary": self.binary,
            "available": self.is_available,
            "capabilities": [c.value for c in self.capabilities],
            "modes": self.modes,
            "default_model": self.default_model,
            "default_instance": self.default_instance,
            "instances": [i.summary() for i in self.instances],
            "description": self.description,
        }


# ── Built-in tool definitions ────────────────────────────


def _host_instance(binary: str, description: str) -> ToolInstance:
    return ToolInstance(
        name="host",
        kind="host",
        command=[binary] if binary else [],
        description=description,
    )


def _docker_instance(
    *,
    service: str,
    profile: str,
    command: list[str],
    description: str,
    container_name: str = "",
    notes: str = "",
) -> ToolInstance:
    return ToolInstance(
        name="docker",
        kind="docker",
        command=command,
        service=service,
        profile=profile,
        container_name=container_name,
        description=description,
        notes=notes,
    )

_BUILTINS: list[ToolDefinition] = [
    ToolDefinition(
        name="aider",
        category=ToolCategory.AGENT_CLI,
        binary="aider",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.REFACTOR,
            ToolCapability.TEST_RUN, ToolCapability.CHAT,
        ],
        default_model="smart",
        modes=["code", "architect", "ask"],
        instances=[
            _host_instance("aider", "Run the aider CLI directly on the host."),
            _docker_instance(
                service="aider",
                profile="tools",
                command=["docker", "compose", "--profile", "tools", "run", "--rm", "aider"],
                container_name="proxym-aider",
                description="Run aider as a docker-backed service instance.",
                notes="Compose service placeholder: add a matching 'aider' service/image when you want container execution.",
            ),
        ],
        description="AI pair programming in the terminal",
    ),
    ToolDefinition(
        name="claude-code",
        category=ToolCategory.AGENT_CLI,
        binary="claude",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.REFACTOR,
            ToolCapability.FILE_CREATE, ToolCapability.CHAT,
            ToolCapability.DEBUG, ToolCapability.ARCHITECT,
        ],
        default_model="smart",
        modes=["code", "architect", "debug", "ask"],
        instances=[
            _host_instance("claude", "Run the Claude Code CLI directly on the host."),
            _docker_instance(
                service="claude-code",
                profile="tools",
                command=["docker", "compose", "--profile", "tools", "run", "--rm", "claude-code"],
                container_name="proxym-claude-code",
                description="Run Claude Code as a docker-backed service instance.",
                notes="Compose service placeholder: add a matching 'claude-code' service/image when you want container execution.",
            ),
        ],
        description="Anthropic Claude Code CLI agent",
    ),
    ToolDefinition(
        name="windsurf",
        category=ToolCategory.IDE,
        binary="windsurf",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.REFACTOR,
            ToolCapability.FILE_CREATE, ToolCapability.CHAT,
        ],
        default_model="smart",
        modes=["code", "architect"],
        instances=[
            _host_instance("windsurf", "Launch the Windsurf desktop app on the host."),
            _docker_instance(
                service="windsurf",
                profile="tools",
                command=["docker", "compose", "--profile", "tools", "run", "--rm", "windsurf"],
                container_name="proxym-windsurf",
                description="Docker service descriptor for a Windsurf-compatible instance.",
                notes="Placeholder command for a future Windsurf service image or wrapper container.",
            ),
        ],
        description="Codeium Windsurf IDE with Cascade agent",
    ),
    ToolDefinition(
        name="cursor",
        category=ToolCategory.IDE,
        binary="cursor",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.REFACTOR,
            ToolCapability.FILE_CREATE, ToolCapability.CHAT,
        ],
        default_model="smart",
        modes=["code", "architect"],
        instances=[
            _host_instance("cursor", "Launch the Cursor desktop app on the host."),
            _docker_instance(
                service="cursor",
                profile="tools",
                command=["docker", "compose", "--profile", "tools", "run", "--rm", "cursor"],
                container_name="proxym-cursor",
                description="Docker service descriptor for a Cursor-compatible instance.",
                notes="Placeholder command for a future Cursor service image or wrapper container.",
            ),
        ],
        description="Cursor IDE with Composer agent",
    ),
    ToolDefinition(
        name="vscode",
        category=ToolCategory.IDE,
        binary="code",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.CHAT,
        ],
        default_model="smart",
        modes=["code"],
        default_instance="docker",
        instances=[
            _host_instance("code", "Open VS Code on the host."),
            _docker_instance(
                service="vscode",
                profile="vscode",
                command=["docker", "compose", "--profile", "vscode", "up", "-d", "vscode"],
                container_name="proxym-vscode",
                description="VS Code Web running as the proxym Docker service.",
                notes="This is the primary container-backed instance used by Roo Code and Cline.",
            ),
        ],
        description="VS Code with Roo Code / Continue extension",
    ),
    ToolDefinition(
        name="roo-code",
        category=ToolCategory.IDE,
        binary="code",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.REFACTOR,
            ToolCapability.CHAT, ToolCapability.DEBUG,
        ],
        default_model="smart",
        modes=["code", "architect", "debug", "ask"],
        default_instance="docker",
        instances=[
            _host_instance("code", "Use the local VS Code binary if available."),
            _docker_instance(
                service="vscode",
                profile="vscode",
                command=["docker", "compose", "--profile", "vscode", "up", "-d", "vscode"],
                container_name="proxym-vscode",
                description="Roo Code running inside the proxym VS Code Web container.",
                notes="Shares the same docker service as the main VS Code instance.",
            ),
        ],
        description="Roo Code extension in VS Code Web",
    ),
    ToolDefinition(
        name="cline",
        category=ToolCategory.IDE,
        binary="code",
        capabilities=[
            ToolCapability.CODE_EDIT, ToolCapability.CHAT,
        ],
        default_model="smart",
        modes=["code"],
        default_instance="docker",
        instances=[
            _host_instance("code", "Use the local VS Code binary if available."),
            _docker_instance(
                service="vscode",
                profile="vscode",
                command=["docker", "compose", "--profile", "vscode", "up", "-d", "vscode"],
                container_name="proxym-vscode",
                description="Cline running inside the proxym VS Code Web container.",
                notes="Shares the same docker service as the main VS Code instance.",
            ),
        ],
        description="Cline extension in VS Code",
    ),
    ToolDefinition(
        name="browser",
        category=ToolCategory.BROWSER,
        binary="",
        capabilities=[ToolCapability.CHAT],
        default_model="smart",
        modes=[],
        default_instance="docker",
        instances=[
            _docker_instance(
                service="open-webui",
                profile="webui",
                command=["docker", "compose", "--profile", "webui", "up", "-d", "open-webui"],
                container_name="proxym-open-webui",
                description="Browser-based chat service backed by Open WebUI.",
                notes="This is the docker-native browser/chat instance shipped with proxym.",
            ),
        ],
        description="Browser-based chat (Open WebUI, ChatGPT, etc.)",
    ),
]


# ── Registry ─────────────────────────────────────────────

class ToolRegistry:
    """Singleton registry of available development tools."""

    _instance: ToolRegistry | None = None

    def __init__(self) -> None:
        self._tools: dict[str, ToolDefinition] = {}
        for t in _BUILTINS:
            self._tools[t.name] = copy.copy(t)
        self._apply_env_overrides()
        logger.info("tool_registry_init", count=len(self._tools),
                    available=len(self.list_available()))

    def _apply_env_overrides(self) -> None:
        """Mark tools available via PROXYM_TOOLS_AVAILABLE env var.

        Value: comma-separated tool names, or ``*`` for all.
        Useful when the server runs inside a container but dispatches
        to host-side binaries.
        """
        raw = os.environ.get("PROXYM_TOOLS_AVAILABLE", "").strip()
        if not raw:
            return
        if raw == "*":
            names = set(self._tools.keys())
        else:
            names = {n.strip() for n in raw.split(",") if n.strip()}
        for name in names:
            tool = self._tools.get(name)
            if tool:
                tool.force_available = True
                logger.debug("tool_force_available", name=name)

    @classmethod
    def get(cls) -> ToolRegistry:
        """Return singleton instance."""
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance

    def register(self, tool: ToolDefinition) -> None:
        """Register or override a tool definition."""
        self._tools[tool.name] = tool
        logger.info("tool_registered", name=tool.name)

    def unregister(self, name: str) -> bool:
        """Remove a tool. Returns False if not found."""
        if name in self._tools:
            del self._tools[name]
            return True
        return False

    def get_tool(self, name: str) -> ToolDefinition | None:
        return self._tools.get(name)

    def list_tools(
        self,
        category: ToolCategory | None = None,
        available_only: bool = False,
    ) -> list[ToolDefinition]:
        tools = list(self._tools.values())
        if category:
            tools = [t for t in tools if t.category == category]
        if available_only:
            tools = [t for t in tools if t.is_available]
        return sorted(tools, key=lambda t: t.name)

    def list_available(self) -> list[ToolDefinition]:
        return self.list_tools(available_only=True)

    def summary(self) -> dict[str, Any]:
        tools = self.list_tools()
        available = [t for t in tools if t.is_available]
        return {
            "total": len(tools),
            "available": len(available),
            "tools": [t.summary() for t in tools],
        }
