"""Researcher-side runner: bridge between the platform API and Claude Code.

This isn't an autonomous agent — it's a context provider. It fetches the
project state from the API and prints structured context that Claude Code
(or any coding agent) uses to decide what to try next.

The workflow:
1. runner prints project state, experiment history, queue, current code
2. Claude Code reads the context, edits train.py, runs training
3. Sidecar watches results.tsv and pushes to the API
4. Runner detects new results, prints updated context
5. Repeat

Usage from Claude Code:
  $ nbench run --project my-project --watch

The --watch flag keeps it running, printing fresh context after each experiment.
Without --watch, it prints context once and exits (useful for one-shot).
"""

from __future__ import annotations

import os
import sys
import time

from .client import NbenchClient


def _print_context(client: NbenchClient, project_id: str, train_file: str | None) -> int:
    """Fetch and print the full project context. Returns the current experiment count."""
    project = client.get_project(project_id)
    experiments = client.get_experiments(project_id, limit=30)
    categories = client.get_categories(project_id)

    # Check queue for external suggestions
    queue = []
    try:
        queue = client.get_queue(project_id)
        queue = [s for s in queue if s.get("status") in ("queued", "injected")]
    except Exception:
        pass

    print("\n" + "=" * 60)
    print(f"PROJECT: {project.get('name', '?')}")
    print(f"SLUG:    {project.get('slug', '?')}")
    print(f"BEST:    {project.get('current_best_val_bpb', '?')}")
    print(f"EXPS:    {project.get('total_experiments', 0)} total, {project.get('total_keeps', 0)} keeps")
    print("=" * 60)

    # External suggestions waiting
    if queue:
        print(f"\n>> {len(queue)} SUGGESTION(S) IN QUEUE — try these first:")
        for i, s in enumerate(queue[:5]):
            contributor = s.get("contributor_handle", "unknown")
            text = s.get("prompt_text", "")[:200]
            print(f"   [{i+1}] @{contributor}: {text}")
        print()

    # What's been tried
    if categories:
        print("CATEGORIES:")
        for cat in categories:
            print(f"  {cat.get('category', '?')}: {cat.get('keeps', 0)} keeps / {cat.get('total', 0)} total")
        print()

    # Recent experiments
    if experiments:
        print("RECENT EXPERIMENTS:")
        for exp in experiments[:20]:
            status = exp.get("status", "?")
            bpb = exp.get("val_bpb")
            bpb_str = f"{bpb:.4f}" if bpb is not None else "crash"
            desc = exp.get("description", "")[:80]
            marker = " <<<" if status == "keep" else ""
            print(f"  #{exp.get('experiment_number', '?'):3d} [{status:7s}] {bpb_str}  {desc}{marker}")
        print()

    # Current code
    if train_file and os.path.exists(train_file):
        with open(train_file) as f:
            code = f.read()
        print(f"CURRENT CODE ({train_file}, {len(code)} chars):")
        print("```python")
        # Show first 100 lines
        lines = code.split("\n")
        for line in lines[:100]:
            print(line)
        if len(lines) > 100:
            print(f"... ({len(lines) - 100} more lines)")
        print("```")
        print()

    # Instructions
    if queue:
        print("ACTION: Apply the first queued suggestion to the training code,")
        print("        run training, and check if val_bpb improves.")
    elif experiments:
        print("ACTION: Based on the experiment history above, try something new.")
        print("        Edit the training code, run it, check results.")
    else:
        print("ACTION: This is a fresh project. Run the training code as-is")
        print("        to establish the first experiment, then start optimising.")

    print(f"\nRun training with: python {train_file or 'train.py'}")
    print(f"Results will be picked up by the sidecar automatically.")
    print("=" * 60)

    return project.get("total_experiments", 0)


def run_runner(
    api_url: str,
    api_key: str,
    project_id: str,
    train_file: str = "train.py",
    train_command: str | None = None,
    results_file: str = "results.tsv",
    provider_name: str = "anthropic",
    model: str | None = None,
    interval: int = 30,
    max_runs: int = 0,
    watch: bool = False,
) -> None:
    """Print project context for Claude Code to act on.

    With --watch: continuously monitors for new experiments and reprints context.
    Without --watch: prints context once and exits.
    """
    client = NbenchClient(api_url=api_url, api_key=api_key)

    print(f"[runner] Fetching project state: {project_id}")

    last_exp_count = _print_context(client, project_id, train_file)

    if not watch:
        return

    # Watch mode: poll for new experiments and reprint context
    print(f"\n[runner] Watching for new experiments (poll every {interval}s)...")
    print(f"[runner] Edit {train_file}, run training, and context will refresh.\n")

    while True:
        try:
            time.sleep(interval)
            project = client.get_project(project_id)
            current_count = project.get("total_experiments", 0)

            if current_count > last_exp_count:
                new = current_count - last_exp_count
                print(f"\n[runner] {new} new experiment(s) detected! Refreshing context...\n")
                last_exp_count = _print_context(client, project_id, train_file)
        except KeyboardInterrupt:
            print(f"\n[runner] Stopped watching.")
            break
        except Exception as e:
            print(f"[runner] Error: {e}")
