"""CLI commands for viewing and managing tools."""

import asyncio
from typing import Annotated

import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.text import Text

from cadecoder.core.config import is_verbose
from cadecoder.tools.manager import CompositeToolManager

console = Console()


def _get_tool_name(tool: dict) -> str:
    """Extract tool name from tool dict."""
    return tool.get("function", {}).get("name", "unknown")


def _get_tool_description(tool: dict) -> str:
    """Extract and clean tool description."""
    desc = tool.get("function", {}).get("description", "")

    # Remove bracket prefix if present
    if desc.startswith("["):
        bracket_end = desc.find("]")
        if bracket_end != -1:
            desc = desc[bracket_end + 1 :].strip()

    # Cut at first newline
    if "\n" in desc:
        desc = desc.split("\n")[0].strip()

    return desc


async def _load_tools() -> tuple[
    list[dict], dict[str, str], dict[str, str], list[tuple[str, str | None]]
]:
    """Load tools from the composite manager."""
    tool_manager = CompositeToolManager(enable_mcp=True, local_only=False)
    tools = await tool_manager.get_tools()

    source_map: dict[str, str] = {}
    server_map: dict[str, str] = {}

    for info in tool_manager.get_all_tool_info():
        tool_name = info["name"]
        source_map[tool_name] = info["source"]
        server_map[tool_name] = info.get("server", "")

    auth_warnings = getattr(tool_manager, "_auth_warnings", [])

    await tool_manager.close()
    return tools, source_map, server_map, auth_warnings


def _display_tools(
    tools: list[dict],
    source_map: dict[str, str],
    server_filter: str | None,
    show_all: bool,
) -> None:
    """Display tools in a formatted table."""
    from collections import defaultdict

    if not tools:
        console.print("[yellow]No tools found.[/yellow]")
        return

    # Group tools by server/prefix
    grouped_tools: dict[str, list[dict]] = defaultdict(list)

    for tool in tools:
        name = _get_tool_name(tool)
        tool_source = source_map.get(name, "unknown")

        if tool_source == "mcp" and "_" in name:
            server_prefix = name.split("_", 1)[0]
            grouped_tools[server_prefix].append(tool)
        else:
            grouped_tools["Local"].append(tool)

    # Create table (no Source column; all tools are MCP-served)
    table = Table(title="Available Tools", show_lines=False, box=None)
    table.add_column("Name", style="cyan", no_wrap=True, width=45)
    table.add_column("Description", style="white", max_width=50)

    total_count = 0
    shown_count = 0

    for server_name in sorted(grouped_tools.keys()):
        server_tools = sorted(grouped_tools[server_name], key=_get_tool_name)
        server_tool_count = len(server_tools)
        total_count += server_tool_count

        max_to_show = (
            server_tool_count if (server_filter or show_all) else min(3, server_tool_count)
        )
        tools_to_display = server_tools[:max_to_show]
        shown_count += len(tools_to_display)

        header = f"[bold white]{server_name}[/bold white] [dim]({server_tool_count} tools)[/dim]"
        table.add_row(header, "")

        for tool in tools_to_display:
            name = _get_tool_name(tool)
            desc = _get_tool_description(tool)
            if len(desc) > 50:
                desc = desc[:47] + "..."

            table.add_row(f"  {name}", desc)

        if max_to_show < server_tool_count:
            remaining = server_tool_count - max_to_show
            table.add_row(
                f"  [dim]...and {remaining} more[/dim]",
                f"[dim]Use --server {server_name} to see all[/dim]",
            )

        if server_name != sorted(grouped_tools.keys())[-1]:
            table.add_row("", "")

    console.print(table)

    if shown_count < total_count:
        console.print(
            f"\n[dim]Showing {shown_count} of {total_count} tools. "
            f"Use [bold]cade tools list --all[/bold] to see all, or [bold]--server <name>[/bold] for one server.[/dim]"
        )
    else:
        console.print(f"\n[dim]Total: {total_count} tools[/dim]")


def _display_auth_warnings(auth_warnings: list[tuple[str, str | None]]) -> None:
    """Display authorization warnings for servers that need OAuth."""
    for server_name, auth_url in auth_warnings:
        console.print(f"\n[yellow]⚠ Server '{server_name}' requires authorization.[/yellow]")
        if auth_url:
            console.print(f"  Authorize here: [link={auth_url}]{auth_url}[/link]")
        else:
            console.print(f"  Run [bold]cade mcp auth {server_name}[/bold] to authenticate.")


def _list_tools_impl(
    search: str | None = None,
    server: str | None = None,
    show_all: bool = False,
) -> None:
    """Implementation of tool listing logic."""
    if is_verbose():
        console.print("[cyan]Loading tools (verbose)...[/cyan]")
        tools, source_map, _, auth_warnings = asyncio.run(_load_tools())
    else:
        with console.status("[cyan]Loading tools...", spinner="dots"):
            tools, source_map, _, auth_warnings = asyncio.run(_load_tools())

    # Filter: exclude local (built-in) tools unless --all
    if not show_all:
        tools = [t for t in tools if source_map.get(_get_tool_name(t), "") != "local"]

    # Filter: by server prefix
    if server:
        tools = [t for t in tools if _get_tool_name(t).startswith(f"{server}_")]

    # Filter: by search query
    if search:
        search_lower = search.lower()
        tools = [
            t
            for t in tools
            if search_lower in _get_tool_name(t).lower()
            or search_lower in _get_tool_description(t).lower()
        ]

    _display_tools(tools, source_map, server, show_all)

    if auth_warnings:
        _display_auth_warnings(auth_warnings)


# Create tool command - using a simple Typer app without callback complexity
tool_app = typer.Typer(
    name="tools",
    help="View and manage available tools",
    no_args_is_help=False,
)


@tool_app.command("list")
def list_tools(
    search: Annotated[
        str | None,
        typer.Option("--search", "-q", help="Search tools by name or description"),
    ] = None,
    server: Annotated[
        str | None,
        typer.Option("--server", help="Filter by server name (e.g., Librarian, Local)"),
    ] = None,
    show_all: Annotated[
        bool,
        typer.Option("--all", "-a", help="Show all tools (no truncation)"),
    ] = False,
) -> None:
    """List all available tools."""
    _list_tools_impl(search=search, server=server, show_all=show_all)


@tool_app.command("info")
def tool_info(
    name: Annotated[str, typer.Argument(help="Name of the tool")],
) -> None:
    """Show detailed information about a tool."""

    async def get_tool_details() -> tuple[dict | None, str | None, list[tuple[str, str | None]]]:
        tool_manager = CompositeToolManager(enable_mcp=True, local_only=False)
        tools = await tool_manager.get_tools()

        name_lower = name.strip().lower()
        tool = next(
            (t for t in tools if t.get("function", {}).get("name", "").lower() == name_lower),
            None,
        )
        # Resolve source using actual tool name from dict if found
        actual_name = tool.get("function", {}).get("name", name) if tool else name
        source = tool_manager.get_tool_source(actual_name)
        auth_warnings = getattr(tool_manager, "_auth_warnings", [])

        await tool_manager.close()
        return tool, source, auth_warnings

    if is_verbose():
        console.print("[cyan]Loading tool info (verbose)...[/cyan]")
        tool, source, auth_warnings = asyncio.run(get_tool_details())
    else:
        with console.status("[cyan]Loading tool info...", spinner="dots"):
            tool, source, auth_warnings = asyncio.run(get_tool_details())

    if not tool:
        console.print(f"[red]Tool '{name}' not found.[/red]")
        raise typer.Exit(1)

    func = tool.get("function", {})
    tool_name = func.get("name", "unknown")
    description = func.get("description", "No description")
    parameters = func.get("parameters", {})

    content = Text()

    content.append(f"{tool_name}\n", style="bold cyan")
    if source:
        source_display = {"local": "Local", "mcp": "MCP Server"}.get(source, source.title())
        content.append(f"Source: {source_display}\n\n", style="dim")

    if description.startswith("["):
        bracket_end = description.find("]")
        if bracket_end != -1:
            description = description[bracket_end + 1 :].strip()

    content.append("Description:\n", style="bold")
    content.append(f"{description}\n\n", style="white")

    props = parameters.get("properties", {})
    required = set(parameters.get("required", []))

    if props:
        content.append("Parameters:\n", style="bold")
        for param_name, param_info in props.items():
            param_type = param_info.get("type", "any")
            param_desc = param_info.get("description", "")
            is_required = param_name in required

            req_marker = " [required]" if is_required else ""
            content.append(f"  - {param_name}", style="cyan")
            content.append(f" ({param_type}){req_marker}\n", style="dim")
            if param_desc:
                content.append(f"    {param_desc}\n", style="white")
    else:
        content.append("Parameters: None\n", style="dim")

    panel = Panel(content, title="Tool Info", border_style="blue", padding=(1, 2))
    console.print(panel)

    if auth_warnings:
        _display_auth_warnings(auth_warnings)


@tool_app.command("search")
def search_tools(
    query: Annotated[str, typer.Argument(help="Search query")],
) -> None:
    """Search for tools by name or description."""
    _list_tools_impl(search=query)


# Default command when `cade tools` is called without subcommand
@tool_app.callback(invoke_without_command=True)
def tools_default(
    ctx: typer.Context,
    search: Annotated[
        str | None,
        typer.Option("--search", "-q", help="Search tools by name or description"),
    ] = None,
    server: Annotated[
        str | None,
        typer.Option("--server", help="Filter by server name (e.g., Librarian, Local)"),
    ] = None,
    show_all: Annotated[
        bool,
        typer.Option("--all", "-a", help="Show all tools (no truncation)"),
    ] = False,
) -> None:
    """View available tools."""
    if ctx.invoked_subcommand is None:
        _list_tools_impl(search=search, server=server, show_all=show_all)
