import asyncio
import importlib.util
import sys
import click
from mcpbench.bench import MCPBench
from mcpbench.runner import ScenarioResult


def _load_bench(path: str) -> MCPBench:
    spec = importlib.util.spec_from_file_location("_mcpbench_test", path)
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)

    for name in dir(module):
        obj = getattr(module, name)
        if isinstance(obj, MCPBench):
            return obj

    raise click.ClickException(f"No MCPBench instance found in {path}")


def _print_results(description: str, result: ScenarioResult | None, error: Exception | None, threshold: float):
    if error:
        click.echo(f"✗ {description}")
        click.echo(f"  {error}")
        return False

    n = len(result.runs)
    hits = sum(1 for r in result.runs if r.tool_called == result.expected_tool)
    rate = hits / n
    bar = "█" * hits + "░" * (n - hits)
    passed = rate >= threshold

    mark = "✓" if passed else "✗"
    click.echo(f"{mark} {description:<45} {bar}  {hits}/{n}  ({rate:.0%})")

    if not passed:
        click.echo(f"  ← below threshold ({threshold:.0%})")
        for tool, count in sorted(result.confusion.items(), key=lambda x: -x[1]):
            is_correct = tool == result.expected_tool
            marker = "✓" if is_correct else "✗"
            click.echo(f"    called {tool}: {count}x {marker}")

    return passed


@click.group()
def cli():
    pass


@cli.command()
@click.argument("path")
@click.option("--runs", default=5, show_default=True, help="Number of times to run each scenario")
@click.option("--threshold", default=0.8, show_default=True, help="Minimum hit rate to pass")
def run(path: str, runs: int, threshold: float):
    """Run mcpbench scenarios from a test file."""
    bench = _load_bench(path)
    bench.runs = runs

    click.echo(f"\nRunning {len(bench._scenarios)} scenario(s) × {runs} runs each...\n")

    all_passed = True
    for s in bench._scenarios:
        result = None
        error = None
        try:
            result = asyncio.run(s["fn"]())
        except AssertionError as e:
            error = e
        # if we have a result, always show the bar; error is a secondary note
        passed = _print_results(s["description"], result, error if result is None else None, threshold)
        if not passed:
            all_passed = False

    click.echo()
    if not all_passed:
        click.echo("Some scenarios below threshold.")
        sys.exit(1)
    else:
        click.echo("All scenarios passed.")
