"""Tool management commands for CleverAgents CLI.

The ``agents tool`` command group manages tools (callable operations)
in the CleverAgents tool registry.

## Commands

| Command               | Description                               |
|-----------------------|-------------------------------------------|
| ``agents tool add``   | Register tool from YAML config file       |
| ``agents tool remove``| Remove a tool by namespaced name          |
| ``agents tool list``  | List tools with optional filters          |
| ``agents tool show``  | Show full tool details                    |

## Config-Only Add

Tools are registered **exclusively** via a YAML configuration file:

```bash
agents tool add --config ./tools/line-counter.yaml
```

### YAML Configuration File

```yaml
name: local/line-counter
description: Count lines in a file
source: custom
code: |
  def run(inputs):
      with open(inputs["path"]) as f:
          return {"count": sum(1 for _ in f)}
```

## Error Handling

| Error                      | Cause                                    |
|----------------------------|------------------------------------------|
| ``Config file error``      | File not found or not readable           |
| ``Schema validation error``| YAML does not match Tool schema          |
| ``Duplicate tool``         | Tool name already registered             |

Based on implementation_plan.md -- Task C1.tool.cli.
"""

from __future__ import annotations

import re
from collections import OrderedDict
from pathlib import Path
from typing import Annotated, Any

import typer
import yaml
from rich.panel import Panel
from rich.table import Table

from cleveragents.cli.formatting import OutputFormat, format_output
from cleveragents.cli.renderers import _get_console
from cleveragents.core.exceptions import (
    CleverAgentsError,
    NotFoundError,
    ValidationError,
)
from cleveragents.domain.models.core.tool import Tool, ToolType

# Create sub-app for tool commands
app = typer.Typer(help="Manage tools (callable operations) in the tool registry.")
console = _get_console()

# Reusable --format option description
_FORMAT_HELP = "Output format: json, yaml, plain, table, or rich (default: rich)"


def _get_tool_registry_service() -> Any:
    """Get the ToolRegistryService from the container."""
    from cleveragents.application.container import get_container

    container = get_container()
    database_url: str = container.database_url()

    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    from cleveragents.application.services.tool_registry_service import (
        ToolRegistryService,
    )
    from cleveragents.infrastructure.database.repositories import (
        ToolRegistryRepository,
        ValidationAttachmentRepository,
    )

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    tool_repo = ToolRegistryRepository(session_factory=factory)
    attachment_repo = ValidationAttachmentRepository(session_factory=factory)
    return ToolRegistryService(
        tool_repo=tool_repo,
        attachment_repo=attachment_repo,
    )


def _tool_spec_dict(tool: Any) -> OrderedDict[str, Any]:
    """Return tool data as a dict for CLI rendering.

    Supports both domain model objects (with ``as_cli_dict``) and
    plain dicts returned by the repository layer.
    """
    if hasattr(tool, "as_cli_dict"):
        return tool.as_cli_dict()

    # Handle dict-like objects from the repository
    if isinstance(tool, dict):
        result: OrderedDict[str, Any] = OrderedDict()
        result["name"] = tool.get("name", "")
        result["description"] = tool.get("description", "")
        result["source"] = tool.get("source", "")
        result["tool_type"] = tool.get("tool_type", "tool")
        ns = tool.get("namespace", "")
        sn = tool.get("short_name", "")
        if not ns and "/" in result["name"]:
            ns = str(result["name"]).split("/", 1)[0]
            sn = str(result["name"]).split("/", 1)[1]
        result["namespace"] = ns
        result["short_name"] = sn
        result["capability"] = tool.get("capability", {})
        result["timeout"] = tool.get("timeout", 300)
        # Include validation fields if present
        if tool.get("mode"):
            result["mode"] = tool["mode"]
        if tool.get("wraps"):
            result["wraps"] = tool["wraps"]
        return result

    return OrderedDict({"name": str(tool)})


def _print_tool(
    tool: Any,
    title: str = "Tool",
    fmt: str = OutputFormat.RICH.value,
) -> None:
    """Print tool details in the requested format."""
    if fmt != OutputFormat.RICH.value:
        data = _tool_spec_dict(tool)
        console.print(format_output(dict(data), fmt))
        return

    data = _tool_spec_dict(tool)
    name = data.get("name", "")
    desc = data.get("description", "")
    source = data.get("source", "")
    tool_type = data.get("tool_type", "tool")
    namespace = data.get("namespace", "")
    short_name = data.get("short_name", "")
    timeout = data.get("timeout", 300)

    cap = data.get("capability", {})
    cap_lines: list[str] = []
    if isinstance(cap, dict):
        for cap_key, cap_val in cap.items():
            cap_lines.append(f"  {cap_key}: {cap_val}")
    cap_display = "\n".join(cap_lines) if cap_lines else "  (default)"

    lifecycle = data.get("lifecycle")
    if lifecycle and isinstance(lifecycle, dict):
        lc_lines = [f"  {k}: {v}" for k, v in lifecycle.items() if v]
        lc_display = "\n".join(lc_lines) if lc_lines else "  (none)"
    else:
        lc_display = "  (none)"

    slots = data.get("resource_slots")
    if slots and isinstance(slots, list):
        slot_lines: list[str] = []
        for slot in slots:
            if isinstance(slot, dict):
                slot_lines.append(
                    f"  - {slot.get('name', '?')} "
                    f"({slot.get('resource_type', '?')}, "
                    f"{slot.get('access', '?')})"
                )
        slots_display = "\n".join(slot_lines) if slot_lines else "  (none)"
    else:
        slots_display = "  (none)"

    details = (
        f"[bold]Name:[/bold] {name}\n"
        f"[bold]Namespace:[/bold] {namespace}\n"
        f"[bold]Short Name:[/bold] {short_name}\n"
        f"[bold]Description:[/bold] {desc}\n"
        f"[bold]Source:[/bold] {source}\n"
        f"[bold]Type:[/bold] {tool_type}\n"
        f"[bold]Timeout:[/bold] {timeout}s\n"
        f"[bold]Capability:[/bold]\n{cap_display}\n"
        f"[bold]Resource Slots:[/bold]\n{slots_display}\n"
        f"[bold]Lifecycle:[/bold]\n{lc_display}"
    )

    # Show validation-specific fields
    mode = data.get("mode")
    if mode:
        details += f"\n[bold]Validation Mode:[/bold] {mode}"
    wraps = data.get("wraps")
    if wraps:
        details += f"\n[bold]Wraps:[/bold] {wraps}"

    console.print(Panel(details, title=title, expand=False))


@app.command("add")
def add(
    config: Annotated[
        Path,
        typer.Option(
            "--config",
            "-c",
            help="Path to the tool YAML configuration file",
            exists=False,
        ),
    ],
    update: Annotated[
        bool,
        typer.Option("--update", help="Update if tool already exists"),
    ] = False,
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Register a new tool from a YAML configuration file.

    Tools are registered ONLY via ``--config <file>``.

    Examples:
        agents tool add --config ./tools/line-counter.yaml
        agents tool add --config ./tools/line-counter.yaml --update
    """
    try:
        if not config.exists():
            raise FileNotFoundError(f"Config file not found: {config}")

        raw = config.read_text(encoding="utf-8")
        config_dict = yaml.safe_load(raw)

        # Handle spec-compliant 'tool:' wrapper key format
        # If the YAML has a top-level 'tool:' key, extract its contents
        if isinstance(config_dict, dict) and "tool" in config_dict:
            config_dict = config_dict["tool"]

        # Ignore 'cleveragents:' version header if present
        if isinstance(config_dict, dict) and "cleveragents" in config_dict:
            del config_dict["cleveragents"]
        if not isinstance(config_dict, dict):
            raise ValueError("YAML config must be a mapping")

        tool = Tool.from_config(config_dict)
        service = _get_tool_registry_service()

        if update:
            existing = service.get_tool(tool.name)
            if existing is not None:
                registered = service.update_tool(tool)
                _print_tool(registered, title="Tool Updated", fmt=fmt)
                return

        registered = service.register_tool(tool)
        _print_tool(registered, title="Tool Registered", fmt=fmt)

    except FileNotFoundError as exc:
        console.print(f"[red]Config file error:[/red] {exc}")
        raise typer.Abort() from exc
    except yaml.YAMLError as exc:
        console.print(f"[red]YAML parse error:[/red] {exc}")
        raise typer.Abort() from exc
    except ValueError as exc:
        console.print(f"[red]Schema validation error:[/red] {exc}")
        raise typer.Abort() from exc
    except ValidationError as exc:
        console.print(f"[red]Validation Error:[/red] {exc.message}")
        raise typer.Abort() from exc
    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


@app.command("remove")
def remove(
    name: Annotated[
        str,
        typer.Argument(help="Namespaced name of the tool to remove"),
    ],
    yes: Annotated[
        bool,
        typer.Option("--yes", "-y", help="Skip confirmation prompt"),
    ] = False,
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Remove a tool by namespaced name.

    Examples:
        agents tool remove local/line-counter
        agents tool remove --yes local/line-counter
    """
    try:
        service = _get_tool_registry_service()

        existing = service.get_tool(name)
        if existing is None:
            console.print(f"[red]Tool not found:[/red] {name}")
            raise typer.Abort()

        if not yes:
            confirm = typer.confirm(f"Remove tool '{name}'?")
            if not confirm:
                console.print("[yellow]Aborted.[/yellow]")
                raise typer.Abort()

        removed = service.remove_tool(name)
        if not removed:
            console.print(f"[red]Failed to remove tool:[/red] {name}")
            raise typer.Abort()

        if fmt != OutputFormat.RICH.value:
            data = {"name": name, "removed": True}
            console.print(format_output(data, fmt))
            return

        console.print(f"[green]Removed tool:[/green] {name}")

    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


@app.command("list")
def list_tools(
    namespace: Annotated[
        str | None,
        typer.Option("--namespace", "-n", help="Filter by namespace"),
    ] = None,
    source: Annotated[
        str | None,
        typer.Option("--source", "-s", help="Filter by source type"),
    ] = None,
    tool_type: Annotated[
        str | None,
        typer.Option("--type", "-t", help="Filter by type (tool or validation)"),
    ] = None,
    regex: Annotated[
        str | None,
        typer.Argument(help="Optional regex pattern to filter tool names"),
    ] = None,
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """List tools with optional filters.

    Examples:
        agents tool list
        agents tool list --namespace local
        agents tool list --source mcp
        agents tool list --type validation
        agents tool list "line-.*"
        agents tool list --format json
    """
    try:
        # Validate type filter if provided
        if tool_type is not None:
            try:
                ToolType(tool_type.lower())
            except ValueError as exc:
                console.print(
                    f"[red]Invalid type:[/red] {tool_type}. "
                    "Valid values: tool, validation"
                )
                raise typer.Abort() from exc

        service = _get_tool_registry_service()
        tools = service.list_tools(
            namespace=namespace,
            tool_type=tool_type,
            source=source,
        )

        # Apply regex filter if provided
        if regex:
            try:
                pattern = re.compile(regex)
            except re.error as exc:
                console.print(f"[red]Invalid regex pattern:[/red] {regex}")
                raise typer.Abort() from exc

            def _get_name(t: Any) -> str:
                if isinstance(t, dict):
                    return str(t.get("name", ""))
                return str(getattr(t, "name", ""))

            tools = [t for t in tools if pattern.search(_get_name(t))]

        if not tools:
            console.print("[yellow]No tools found.[/yellow]")
            console.print("Register one with 'agents tool add --config <file>'")
            return

        # Non-rich formats use the formatting helper
        if fmt != OutputFormat.RICH.value:
            data = [dict(_tool_spec_dict(t)) for t in tools]
            console.print(format_output(data, fmt))
            return

        # Rich table
        table = Table(title=f"Tools ({len(tools)} total)")
        table.add_column("Name", style="cyan")
        table.add_column("Type", style="blue")
        table.add_column("Source", style="magenta")
        table.add_column("Description", style="dim")
        table.add_column("Timeout", justify="right")

        for tool in tools:
            spec = _tool_spec_dict(tool)
            desc = str(spec.get("description", ""))
            if len(desc) > 40:
                desc = desc[:37] + "..."
            table.add_row(
                str(spec.get("name", "")),
                str(spec.get("tool_type", "tool")),
                str(spec.get("source", "")),
                desc,
                str(spec.get("timeout", 300)),
            )

        console.print(table)

    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc


@app.command("show")
def show(
    name: Annotated[
        str,
        typer.Argument(help="Namespaced name of the tool to show"),
    ],
    fmt: Annotated[
        str,
        typer.Option("--format", "-f", help=_FORMAT_HELP),
    ] = "rich",
) -> None:
    """Show details for a tool.

    Specify the namespaced name (e.g. 'local/line-counter').
    """
    try:
        service = _get_tool_registry_service()
        tool = service.get_tool(name)

        if tool is None:
            raise NotFoundError(
                resource_type="tool",
                resource_id=name,
            )

        _print_tool(tool, title="Tool Details", fmt=fmt)

    except NotFoundError as exc:
        console.print(f"[red]Tool not found:[/red] {name}")
        raise typer.Abort() from exc
    except CleverAgentsError as exc:
        console.print(f"[red]Error:[/red] {exc.message}")
        raise typer.Abort() from exc
