import json
import os
import sys
from typing import Any, Dict

import click

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


# ── Alias Group ─────────────────────────────────────────────


class AliasedGroup(click.Group):
    """
    Click group that allows short aliases.
    `aalgoi s` works the same as `aalgoi solve`.
    """

    ALIASES = {
        "s":  "solve",
        "e":  "explain",
        "b":  "benchmark",
        "st": "stats",
        "w":  "web",
        "a":  "api",
        "m":  "marketplace",
    }

    def get_command(self, ctx, cmd_name):
        cmd_name = self.ALIASES.get(cmd_name, cmd_name)
        return super().get_command(ctx, cmd_name)

    def resolve_command(self, ctx, args):
        if args and args[0] in self.ALIASES:
            args[0] = self.ALIASES[args[0]]
        return super().resolve_command(ctx, args)


# ── Helpers ─────────────────────────────────────────────────


def _serialize_for_json(obj: Any) -> Any:
    if isinstance(obj, (int, float, str, bool, type(None))):
        return obj
    if isinstance(obj, (list, tuple)):
        if len(obj) > 20:
            return list(obj[:20]) + ["..."]
        return list(obj)
    if isinstance(obj, dict):
        return {str(k): _serialize_for_json(v) for k, v in obj.items()}
    return str(obj)


def _load_data(data_arg) -> Any:
    if data_arg is None:
        return None
    try:
        return json.loads(data_arg)
    except (json.JSONDecodeError, TypeError):
        try:
            with open(data_arg) as f:
                return json.load(f)
        except Exception:
            click.echo("Error: --data must be valid JSON string or file path", err=True)
            sys.exit(1)


# ── Main CLI ────────────────────────────────────────────────


@click.group(cls=AliasedGroup)
@click.version_option("1.2.0", prog_name="aalgoi")
def main():
    """
    AAlgoI — Self-adaptive algorithm intelligence.

    Aliases: s=solve  e=explain  b=benchmark  st=stats  w=web  a=api
    """


# ── Solve ───────────────────────────────────────────────────


@main.command("solve")
@click.argument("description")
@click.option("--data", type=str, default=None, help="Input data as JSON string or file path")
@click.option("--json-output", "-j", is_flag=True, help="Output as JSON")
@click.option("--quiet", "-q", is_flag=True, help="Only print result value")
def cmd_solve(description, data, json_output, quiet):
    """
    Solve a problem from natural language description.

    Examples:

    \b
        aalgoi solve "sort" 3 1 4 1 5
        aalgoi s "sort desc" 5 3 1
        aalgoi s "path A to D" --data '{"A":{"B":1}}'
    """
    from aalgoi import solve

    input_data = _load_data(data)

    if input_data is None:
        input_data = [3, 1, 4, 1, 5]

    result = solve(description, input_data)

    if quiet:
        click.echo(result.value)
    elif json_output:
        click.echo(json.dumps(result.to_dict(), indent=2))
    else:
        status = click.style("OK", fg="green") if result.ok else click.style("FAIL", fg="red")
        click.echo(f"[{status}] {result.algo} in {result.ms:.2f}ms")
        value = result.value
        if isinstance(value, list) and len(value) > 20:
            click.echo(f"  Result: {value[:20]} ... ({len(value)} total)")
        else:
            click.echo(f"  Result: {value}")
        if result.answer:
            click.echo(f"  {result.answer}")


# ── Explain ─────────────────────────────────────────────────


@main.command("explain")
@click.argument("algorithm")
@click.option("--detail", type=click.Choice(["short", "detailed"]), default="short")
def cmd_explain(algorithm, detail):
    """
    Explain how an algorithm works.

    Examples:

    \b
        aalgoi explain quicksort
        aalgoi e timsort --detail detailed
    """
    from core.explainer import Explainer

    explainer = Explainer()
    exp = explainer.explain(algorithm, detail=detail)

    click.echo()
    click.echo("=" * 60)
    click.echo(f"  {exp.algorithm_name}")
    click.echo("=" * 60)
    click.echo(f"\n  {exp.summary}")
    click.echo(f"\n  Complexity: {exp.complexity}")
    click.echo(f"\n  Best For: {exp.best_for}")
    if exp.steps:
        click.echo("\n  Steps:")
        for i, step in enumerate(exp.steps, 1):
            click.echo(f"    {i}. {step}")
    click.echo()


# ── Benchmark ───────────────────────────────────────────────


@main.command("benchmark")
@click.argument("problem")
@click.option("--n", default=1000, help="Data size")
@click.option("--runs", "-r", default=5, help="Number of runs")
def cmd_benchmark(problem, n, runs):
    """
    Benchmark AAlgoI vs standard library.

    Examples:

    \b
        aalgoi benchmark sort --n 10000
        aalgoi b sort --n 1000 --runs 10
    """
    import random
    from aalgoi import benchmark, ProblemSpec, ProblemType

    data = [random.randint(0, 10**6) for _ in range(n)]
    spec = ProblemSpec(name=problem, problem_type=ProblemType.SORTING)

    click.echo(f"\nBenchmark: {problem} (n={n}, runs={runs})")
    click.echo("-" * 40)

    aalgoi_times = []
    baseline_times = []
    for _ in range(runs):
        bm = benchmark(spec, data)
        aalgoi_times.append(bm.get("aalgoi_time_ms", 0))
        baseline_times.append(bm.get("baseline_time_ms", 0))

    avg_aalgoi = sum(aalgoi_times) / runs
    avg_baseline = sum(baseline_times) / runs
    speedup = avg_baseline / avg_aalgoi if avg_aalgoi > 0 else 0

    winner_color = "green" if speedup > 1.05 else "yellow"
    click.echo(f"  AAlgoI:   {avg_aalgoi:.3f}ms avg")
    click.echo(f"  Baseline: {avg_baseline:.3f}ms avg (stdlib)")
    click.echo(f"  Speedup:  {speedup:.2f}x")
    click.echo(click.style(f"  Winner:   {'AAlgoI' if speedup > 1.05 else 'Baseline'}", fg=winner_color))


# ── Stats ───────────────────────────────────────────────────


@main.command("stats")
def cmd_stats():
    """Show solver performance statistics."""
    from core.smart_solver import SmartSolver

    solver = SmartSolver()
    stats = solver.solver.get_stats()
    click.echo(json.dumps(stats, indent=2, default=str))


# ── Web UI ──────────────────────────────────────────────────


@main.command("web")
@click.option("--port", type=int, default=7860, help="Port number")
@click.option("--share", is_flag=True, help="Generate public link")
def cmd_web(port, share):
    """Launch Gradio web UI."""
    try:
        from interface.web_ui import launch

        launch(share=share, server_port=port)
    except ImportError:
        click.echo("Gradio not available. Install with: pip install gradio", err=True)
        sys.exit(1)


# ── API Server ──────────────────────────────────────────────


@main.command("api")
@click.option("--port", type=int, default=8000, help="Port number")
@click.option("--host", type=str, default="0.0.0.0", help="Host address")
def cmd_api(host, port):
    """Launch FastAPI REST API."""
    try:
        from interface.api import run

        run(host=host, port=port)
    except ImportError:
        click.echo("FastAPI not available. Install with: pip install fastapi uvicorn", err=True)
        sys.exit(1)


# ── Marketplace ─────────────────────────────────────────────


@main.group("marketplace")
def marketplace():
    """Community algorithm marketplace."""


@marketplace.command("list")
def list_marketplace():
    """List all registered algorithms."""
    from core.registry_manager import DynamicRegistry
    from core.smart_solver import SmartSolver

    solver = SmartSolver()
    registry = DynamicRegistry(solver.solver.registry)
    algos = registry.list_algorithms()
    click.echo(f"\nRegistered Algorithms ({len(algos)}):")
    for name in sorted(algos):
        click.echo(f"  - {name}")
    click.echo()


@marketplace.command("search")
@click.argument("query")
def search_marketplace(query):
    """Search for algorithms by keyword."""
    from core.algorithm_marketplace import AlgorithmMarketplace

    mkt = AlgorithmMarketplace()
    results = mkt.find_by_use_case(query)

    if results:
        click.echo(f"\nFound {len(results)} algorithms for '{query}':")
        for meta in results[:10]:
            click.echo(f"  - {meta.name}")
            click.echo(f"    Use case: {meta.use_case[:80]}")
    else:
        click.echo(f"No results for '{query}'")


# ── Subcommands ─────────────────────────────────────────────


# Lazy-import subcommands to avoid circular imports
from interface.cli_debug import debug
from interface.cli_ml import ml

main.add_command(debug)
main.add_command(ml)


if __name__ == "__main__":
    main()
