"""Click CLI for the Nbench platform."""

from __future__ import annotations

import os
import sys

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

from .client import NbenchClient
from .auth import load_credentials, CREDENTIALS_PATH
from .config import load_config

console = Console()

DEFAULT_PROJECT = os.environ.get("NB_PROJECT_ID", "")


def _resolve_api_url(explicit: str | None) -> str:
    """Get API URL from flag, env, saved credentials, or default."""
    if explicit:
        return explicit
    env = os.environ.get("NB_API_URL")
    if env:
        return env
    creds = load_credentials()
    if creds.get("api_url"):
        return creds["api_url"]
    return "http://localhost:8080"


def _resolve_api_key(explicit: str | None) -> str:
    """Get API key from flag, env, or saved credentials."""
    if explicit:
        return explicit
    env = os.environ.get("NB_API_KEY")
    if env:
        return env
    creds = load_credentials()
    return creds.get("api_key", "")


def _resolve_project(explicit: str | None) -> str:
    """Get project slug from flag, env, or .nbench.yml in current directory."""
    if explicit:
        return explicit
    env = os.environ.get("NB_PROJECT_ID")
    if env:
        return env
    # Try reading from .nbench.yml
    config = load_config(".")
    if config.get("project"):
        return config["project"]
    return ""


@click.group()
@click.option("--api-url", default=None, help="API base URL")
@click.option("--api-key", default=None, help="API key for authentication")
@click.pass_context
def main(ctx, api_url: str | None, api_key: str | None):
    """Nbench — agent-first optimisation platform."""
    ctx.ensure_object(dict)
    resolved_url = _resolve_api_url(api_url)
    resolved_key = _resolve_api_key(api_key)
    ctx.obj["api_url"] = resolved_url
    ctx.obj["api_key"] = resolved_key
    ctx.obj["client"] = NbenchClient(api_url=resolved_url, api_key=resolved_key)


# ------------------------------------------------------------------
# login
# ------------------------------------------------------------------

@main.command()
@click.pass_context
def login(ctx):
    """Sign in with GitHub. Opens your browser for OAuth."""
    from .auth import run_login

    result = run_login(api_url=ctx.obj["api_url"])
    if result:
        console.print(f"[green]Signed in as @{result['handle']}[/green]")
        console.print(f"Credentials saved to {CREDENTIALS_PATH}")
        ctx.obj["api_key"] = result["api_key"]
        ctx.obj["client"] = NbenchClient(api_url=ctx.obj["api_url"], api_key=result["api_key"])
    else:
        console.print("[red]Login failed[/red]")
        sys.exit(1)


# ------------------------------------------------------------------
# init
# ------------------------------------------------------------------

@main.command()
@click.pass_context
def init(ctx):
    """Create a new research project interactively."""
    from .init_project import run_init

    result = run_init(api_url=ctx.obj["api_url"], api_key=ctx.obj["api_key"])
    if result:
        project = result.get("project", {})
        console.print(f"\n[green]Project ready![/green] View at: {ctx.obj['api_url'].replace(':8080', ':3000')}/projects/{project.get('slug', '')}")
    else:
        sys.exit(1)


# ------------------------------------------------------------------
# contribute — the main command for Claude Code contributors
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug (auto-detected from .nbench.yml)")
@click.option("--sync", is_flag=True, help="Pull latest main and rebase before showing context")
@click.pass_context
def contribute(ctx, project, sync):
    """Get project context and contribute an optimisation.

    Run this in a cloned repo. Shows what's been tried, current best,
    and instructions for Claude Code.

    Use --sync to pull latest main first.
    """
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red] Use --project SLUG or add 'project: slug' to .nbench.yml")
        sys.exit(1)

    from .contribute import run_contribute
    run_contribute(
        api_url=ctx.obj["api_url"],
        api_key=ctx.obj["api_key"],
        project_slug=slug,
        repo_path=".",
        sync=sync,
    )


# ------------------------------------------------------------------
# submit — register a pushed branch
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.option("--metric", default=None, type=float, help="Metric value (auto-read from results file if omitted)")
@click.option("--message", "-m", default="", help="Commit message / description")
@click.option("--run-training", is_flag=True, help="Run training before submitting")
@click.pass_context
def submit(ctx, project, metric, message, run_training):
    """Submit your changes: branch, commit, push, and register.

    Does everything in one step:
    1. Creates a nbench/* branch if not already on one
    2. Optionally runs training (--run-training)
    3. Reads metric from results file (or use --metric)
    4. Commits and pushes
    5. Registers with the platform

    After Claude Code has edited the code, just run: nbench submit
    """
    import subprocess

    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    config = load_config(".")
    results_file = config.get("results", "results.tsv")
    train_cmd = config.get("train", "python train.py")

    # Get handle
    handle = "contributor"
    try:
        account = ctx.obj["client"].get_account()
        handle = account.get("handle", "contributor")
    except Exception:
        pass

    # Run training if requested
    if run_training:
        console.print(f"[blue]Running:[/blue] {train_cmd}")
        result = subprocess.run(train_cmd, shell=True, capture_output=True, text=True, timeout=3600)
        if result.returncode != 0:
            console.print(f"[red]Training failed:[/red] {result.stderr[:300]}")
            sys.exit(1)
        for line in result.stdout.strip().split("\n")[-3:]:
            console.print(f"  {line}")

    # Read metric from results file if not provided
    if metric is None:
        from .contribute import _parse_last_metric
        metric = _parse_last_metric(results_file)
        if metric is None:
            console.print(f"[red]No metric found in {results_file}.[/red] Use --metric VALUE or --run-training")
            sys.exit(1)
        console.print(f"[blue]Metric from {results_file}:[/blue] {metric}")

    # Get/create branch
    result = subprocess.run(["git", "branch", "--show-current"], capture_output=True, text=True)
    branch = result.stdout.strip()

    if branch in ("main", "master", ""):
        safe_msg = (message or "optimisation").lower().replace(" ", "-")[:30]
        branch = f"nbench/{handle}/{safe_msg}"
        subprocess.run(["git", "checkout", "-b", branch], capture_output=True)
        console.print(f"[blue]Created branch:[/blue] {branch}")

    # Stage and commit
    subprocess.run(["git", "add", "-A"], capture_output=True)
    result = subprocess.run(["git", "diff", "--cached", "--quiet"], capture_output=True)
    if result.returncode != 0:
        commit_msg = message or f"optimisation: metric={metric}"
        subprocess.run(["git", "commit", "-m", commit_msg], capture_output=True)
        console.print(f"[blue]Committed:[/blue] {commit_msg}")

    # Push
    result = subprocess.run(["git", "push", "origin", branch], capture_output=True, text=True)
    if result.returncode != 0:
        # Try setting upstream
        result = subprocess.run(["git", "push", "-u", "origin", branch], capture_output=True, text=True)
        if result.returncode != 0:
            console.print(f"[red]Push failed:[/red] {result.stderr[:200]}")
            sys.exit(1)
    console.print(f"[blue]Pushed:[/blue] {branch}")

    # Register with platform
    try:
        res = ctx.obj["client"].register_branch(slug, branch, description=message, claimed_metric=metric)
        console.print(f"\n[green]Submitted![/green] Branch ID: {res.get('id', '?')}")
        console.print(f"Metric: {metric}")
        console.print(f"Status: pending — waiting for verification")
    except Exception as e:
        console.print(f"[red]Registration failed:[/red] {e}")
        sys.exit(1)


# ------------------------------------------------------------------
# watch — researcher's verification agent
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.option("--repo-path", default=".", help="Path to git repo")
@click.option("--max-concurrent", default=3, type=int, help="Max branches to verify simultaneously")
@click.option("--interval", default=30, type=int, help="Seconds between polls")
@click.pass_context
def watch(ctx, project, repo_path, max_concurrent, interval):
    """Verify contributor branches in isolated git worktrees.

    Run this in your project repo. It polls for new branches,
    tests them in isolated worktrees, and auto-merges improvements.
    """
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    from .watch import run_watch
    run_watch(
        api_url=ctx.obj["api_url"],
        api_key=ctx.obj["api_key"],
        project_id=slug,
        repo_path=repo_path,
        max_concurrent=max_concurrent,
        interval=interval,
    )


# ------------------------------------------------------------------
# run — researcher context printer
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.option("--train-file", default="train.py", help="Training script")
@click.option("--watch-mode", "watch_flag", is_flag=True, help="Keep running, refresh on new experiments")
@click.option("--interval", default=30, type=int, help="Seconds between polls")
@click.pass_context
def run(ctx, project, train_file, watch_flag, interval):
    """Show project context for the researcher's Claude Code session."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    from .runner import run_runner
    run_runner(
        api_url=ctx.obj["api_url"],
        api_key=ctx.obj["api_key"],
        project_id=slug,
        train_file=train_file,
        interval=interval,
        watch=watch_flag,
    )


# ------------------------------------------------------------------
# status
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.confirmation_option(prompt="Are you sure you want to delete this project?")
@click.pass_context
def delete(ctx, project):
    """Delete (deactivate) a project. Historical data is preserved."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    try:
        ctx.obj["client"].deactivate_project(slug)
        console.print(f"[green]Project deactivated.[/green] It's hidden from the homepage but still viewable at its URL.")
    except Exception as e:
        console.print(f"[red]Failed:[/red] {e}")
        sys.exit(1)


@main.command()
@click.option("--project", default=None, help="Project slug")
@click.pass_context
def status(ctx, project):
    """Show project status."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    client: NbenchClient = ctx.obj["client"]
    proj = client.get_project(slug)

    console.print(f"[bold]{proj.get('name', 'Untitled')}[/bold]")
    console.print(f"Slug:         {proj.get('slug', '?')}")
    console.print(f"Status:       {proj.get('status', '-')}")
    console.print(f"Direction:    {proj.get('direction', 'minimize')}")
    console.print(f"Current best: {proj.get('current_best_val_bpb', '?')}")
    console.print(f"Experiments:  {proj.get('total_experiments', 0)}")
    console.print(f"Keeps:        {proj.get('total_keeps', 0)}")
    console.print(f"Contributors: {proj.get('contributor_count', 0)}")

    try:
        branches = client.get_branches(slug)
        pending = sum(1 for b in branches if b["status"] == "pending")
        merged = sum(1 for b in branches if b["status"] == "merged")
        if branches:
            console.print(f"Branches:     {len(branches)} ({merged} merged, {pending} pending)")
    except Exception:
        pass


# ------------------------------------------------------------------
# experiments
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.option("--limit", default=20, type=int, help="Number to show")
@click.pass_context
def experiments(ctx, project, limit):
    """List recent experiments."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    client: NbenchClient = ctx.obj["client"]
    exps = client.get_experiments(slug, limit=limit)

    table = Table(title="Recent Experiments")
    table.add_column("#", style="dim")
    table.add_column("Status")
    table.add_column("Metric", justify="right")
    table.add_column("Contributor")
    table.add_column("Description")

    for exp in exps:
        bpb = exp.get("val_bpb")
        contributor = exp.get("contributor_handle", "")
        table.add_row(
            str(exp.get("experiment_number", "?")),
            exp.get("status", "?"),
            f"{bpb:.6f}" if bpb is not None else "-",
            f"@{contributor}" if contributor else "-",
            (exp.get("description", "") or "")[:50],
        )

    console.print(table)


# ------------------------------------------------------------------
# agent — LLM suggestion loop (legacy)
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.option("--provider", default="anthropic", type=click.Choice(["anthropic", "openai", "local"]))
@click.option("--model", default=None, help="Model name override")
@click.option("--interval", default=300, type=int, help="Seconds between cycles")
@click.pass_context
def agent(ctx, project, provider, model, interval):
    """Run the agent suggestion loop (uses LLM to generate suggestions)."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    from .agent import run_agent
    run_agent(
        api_url=ctx.obj["api_url"],
        api_key=ctx.obj["api_key"],
        project_id=slug,
        provider_name=provider,
        model=model,
        interval=interval,
    )


# ------------------------------------------------------------------
# suggest — one-shot LLM suggestion (legacy)
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.option("--provider", default="anthropic", type=click.Choice(["anthropic", "openai", "local"]))
@click.option("--model", default=None, help="Model name override")
@click.pass_context
def suggest(ctx, project, provider, model):
    """Generate and submit a single suggestion via LLM."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    from .suggest import run_suggest
    result = run_suggest(
        api_url=ctx.obj["api_url"],
        api_key=ctx.obj["api_key"],
        project_id=slug,
        provider_name=provider,
        model=model,
    )
    console.print(f"[green]Suggestion submitted:[/green] {result.get('id', '?')}")


# ------------------------------------------------------------------
# code — show current train.py
# ------------------------------------------------------------------

@main.command()
@click.option("--project", default=None, help="Project slug")
@click.pass_context
def code(ctx, project):
    """Show the current training code."""
    slug = _resolve_project(project)
    if not slug:
        console.print("[red]No project specified.[/red]")
        sys.exit(1)

    client: NbenchClient = ctx.obj["client"]
    source = client.get_code(slug)
    console.print(source)


if __name__ == "__main__":
    main()
