"""CLI entry point for mcp-forge.

Provides the `forge` command with subcommands:
  forge init <command>     — Generate test file from a running server
  forge record <command>   — Record tool responses as fixtures
  forge schema <command>   — Dump tool schemas
  forge check <command>    — Run built-in health checks
"""

from __future__ import annotations

import asyncio
import json
import sys
from pathlib import Path

import click
from rich.console import Console
from rich.table import Table

console = Console()


def _run_async(coro):
    """Run an async coroutine from sync CLI context."""
    return asyncio.run(coro)


@click.group()
@click.version_option(version="0.1.0", prog_name="forge-mcp")
def main():
    """🔨 mcp-forge — pytest for MCP servers."""
    pass


@main.command()
@click.argument("command")
@click.option("-o", "--output", default=None, help="Output test file path.")
@click.option("--timeout", default=10.0, help="Connection timeout in seconds.")
def init(command: str, output: str | None, timeout: float):
    """Generate a test file from a running MCP server.

    Connects to the server, discovers all tools, and generates a complete
    pytest test file with example calls for each tool.
    """
    _run_async(_init_async(command, output, timeout))


async def _init_async(command: str, output: str | None, timeout: float):
    from mcp_forge.client import MCPTestClient
    from mcp_forge.fixtures import generate_test_file

    console.print(f"[bold]🔍 Connecting to server...[/bold]")
    console.print(f"   Command: {command}")

    try:
        async with MCPTestClient.from_command(command, timeout=timeout) as client:
            tools = await client.list_tools()
            console.print(f"   Found [bold green]{len(tools)}[/bold green] tools: ", end="")
            console.print(", ".join(t.name for t in tools))

            if output is None:
                # Auto-generate output filename
                output = "test_mcp_server.py"

            out_path = generate_test_file(tools, command, output)
            console.print(f"\n[bold green]✅ Generated test file:[/bold green] {out_path}")
            console.print(f"   Run with: [bold]pytest {out_path}[/bold]")
    except Exception as exc:
        console.print(f"\n[bold red]❌ Error:[/bold red] {exc}")
        sys.exit(1)


@main.command()
@click.argument("command")
@click.option("-o", "--output", default="./fixtures", help="Output directory.")
@click.option("--timeout", default=10.0, help="Connection timeout in seconds.")
def record(command: str, output: str, timeout: float):
    """Record tool responses as test fixtures.

    Connects to the server, calls each tool with sample inputs,
    and saves the responses as JSON fixture files.
    """
    _run_async(_record_async(command, output, timeout))


async def _record_async(command: str, output: str, timeout: float):
    from mcp_forge.client import MCPTestClient
    from mcp_forge.fixtures import record_fixtures
    from mcp_forge.schema import generate_sample_input

    console.print(f"[bold]🔍 Connecting to server...[/bold]")

    try:
        async with MCPTestClient.from_command(command, timeout=timeout) as client:
            tools = await client.list_tools()
            console.print(
                f"   Found [bold green]{len(tools)}[/bold green] tools"
            )

            console.print(f"\n[bold]📝 Recording tool responses...[/bold]")
            results = {}

            for tool in tools:
                sample = generate_sample_input(tool.input_schema)
                try:
                    result = await client.call_tool(tool.name, sample, timeout=timeout)
                    results[tool.name] = result.to_dict()
                    status = "[green]✓[/green]" if result.is_success else "[yellow]⚠ error response[/yellow]"
                    console.print(f"   {status} {tool.name}")
                except Exception as exc:
                    console.print(f"   [red]✗[/red] {tool.name}: {exc}")

            created = record_fixtures(tools, results, output)
            console.print(
                f"\n[bold green]💾 Saved {len(created)} files to {output}/[/bold green]"
            )
    except Exception as exc:
        console.print(f"\n[bold red]❌ Error:[/bold red] {exc}")
        sys.exit(1)


@main.command()
@click.argument("command")
@click.option(
    "--format",
    "fmt",
    type=click.Choice(["table", "json", "yaml"]),
    default="table",
    help="Output format.",
)
@click.option("-o", "--output", default=None, help="Output file (default: stdout).")
@click.option("--timeout", default=10.0, help="Connection timeout in seconds.")
def schema(command: str, fmt: str, output: str | None, timeout: float):
    """Dump all tool schemas from the server."""
    _run_async(_schema_async(command, fmt, output, timeout))


async def _schema_async(command: str, fmt: str, output: str | None, timeout: float):
    from mcp_forge.client import MCPTestClient

    try:
        async with MCPTestClient.from_command(command, timeout=timeout) as client:
            tools = await client.list_tools()

            if fmt == "json":
                data = {"tools": [t.to_dict() for t in tools]}
                text = json.dumps(data, indent=2, ensure_ascii=False)
                if output:
                    Path(output).write_text(text + "\n", encoding="utf-8")
                    console.print(f"[green]Saved to {output}[/green]")
                else:
                    print(text)

            elif fmt == "yaml":
                try:
                    import yaml
                except ImportError:
                    console.print("[red]PyYAML required for YAML output: pip install pyyaml[/red]")
                    sys.exit(1)
                data = {"tools": [t.to_dict() for t in tools]}
                text = yaml.dump(data, default_flow_style=False)
                if output:
                    Path(output).write_text(text, encoding="utf-8")
                    console.print(f"[green]Saved to {output}[/green]")
                else:
                    print(text)

            else:  # table
                table = Table(title="MCP Server Tools")
                table.add_column("Tool", style="bold")
                table.add_column("Description")
                table.add_column("Required Params")
                table.add_column("Optional Params")

                for tool in sorted(tools, key=lambda t: t.name):
                    req = ", ".join(tool.required_params) or "—"
                    opt_params = set(tool.param_names) - set(tool.required_params)
                    opt = ", ".join(sorted(opt_params)) or "—"
                    table.add_row(
                        tool.name,
                        tool.description or "—",
                        req,
                        opt,
                    )

                console.print(table)

    except Exception as exc:
        console.print(f"\n[bold red]❌ Error:[/bold red] {exc}")
        sys.exit(1)


@main.command()
@click.argument("command")
@click.option("--timeout", default=10.0, help="Per-check timeout in seconds.")
def check(command: str, timeout: float):
    """Run built-in health checks against the server."""
    _run_async(_check_async(command, timeout))


async def _check_async(command: str, timeout: float):
    from mcp_forge.client import MCPTestClient
    from mcp_forge.schema import validate_schema_is_valid

    checks_passed = 0
    checks_failed = 0
    checks_total = 0

    def _pass(msg: str):
        nonlocal checks_passed, checks_total
        checks_total += 1
        checks_passed += 1
        console.print(f"  [green]✓[/green] {msg}")

    def _fail(msg: str):
        nonlocal checks_failed, checks_total
        checks_total += 1
        checks_failed += 1
        console.print(f"  [red]✗[/red] {msg}")

    console.print(f"[bold]🔨 Running health checks...[/bold]\n")

    # Check 1: Server starts and responds
    try:
        async with MCPTestClient.from_command(command, timeout=timeout) as client:
            _pass("Server starts and initializes")

            # Check 2: List tools
            try:
                tools = await client.list_tools()
                _pass(f"tools/list responds ({len(tools)} tools)")
            except Exception as exc:
                _fail(f"tools/list failed: {exc}")
                tools = []

            # Check 3: Tool schemas are valid
            for tool in tools:
                issues = validate_schema_is_valid(tool.input_schema)
                if issues:
                    _fail(f"Tool '{tool.name}' schema issues: {'; '.join(issues)}")
                else:
                    _pass(f"Tool '{tool.name}' schema is valid")

            # Check 4: Tools with required params can be called with sample input
            for tool in tools:
                from mcp_forge.schema import generate_sample_input

                sample = generate_sample_input(tool.input_schema)
                try:
                    result = await client.call_tool(tool.name, sample, timeout=timeout)
                    if result.is_error:
                        _fail(
                            f"Tool '{tool.name}' returned error with sample input: "
                            f"{result.text[:100]}"
                        )
                    else:
                        _pass(f"Tool '{tool.name}' callable with sample input")
                except Exception as exc:
                    _fail(f"Tool '{tool.name}' call failed: {exc}")

            # Check 5: Try listing resources (if supported)
            try:
                resources = await client.list_resources()
                _pass(f"resources/list responds ({len(resources)} resources)")
            except RuntimeError:
                _pass("resources/list: not supported (OK)")
            except Exception as exc:
                _fail(f"resources/list failed: {exc}")

            # Check 6: Try listing prompts (if supported)
            try:
                prompts = await client.list_prompts()
                _pass(f"prompts/list responds ({len(prompts)} prompts)")
            except RuntimeError:
                _pass("prompts/list: not supported (OK)")
            except Exception as exc:
                _fail(f"prompts/list failed: {exc}")

    except Exception as exc:
        _fail(f"Server failed to start: {exc}")

    # Summary
    console.print(f"\n[bold]Results: {checks_passed}/{checks_total} passed[/bold]")
    if checks_failed > 0:
        console.print(f"[red]{checks_failed} checks failed[/red]")
        sys.exit(1)
    else:
        console.print("[green]All checks passed![/green]")


if __name__ == "__main__":
    main()
