#!/usr/bin/env python3
"""
Notebook execution runner for agent tasks.
Wraps papermill with sensible defaults and structured JSON reporting.

Usage as CLI:
    python -m devkit.notebook_runner notebooks/task.ipynb output.ipynb -p threshold 1.0
    python -m devkit.notebook_runner notebooks/task.ipynb --dry-run

Usage as module:
    from devkit.notebook_runner import run_notebook
    result = run_notebook("task.ipynb", "output.ipynb", parameters={"threshold": 1.0})
"""

import argparse
import json
import os
import subprocess
import sys
import tempfile
from pathlib import Path
from typing import Any, Dict, List, Optional


def run_notebook(
    input_path: str,
    output_path: Optional[str] = None,
    parameters: Optional[Dict[str, Any]] = None,
    kernel: str = "python3",
    timeout: Optional[int] = None,
    dry_run: bool = False,
) -> Dict[str, Any]:
    input_path = str(Path(input_path).expanduser().resolve())
    if output_path is None:
        fd, output_path = tempfile.mkstemp(suffix=".ipynb")
        os.close(fd)
    else:
        output_path = str(Path(output_path).expanduser().resolve())

    if dry_run:
        return {
            "status": "dry_run",
            "input": input_path,
            "output": output_path,
            "parameters": parameters or {},
            "kernel": kernel,
        }

    cmd = ["papermill", input_path, output_path, "--kernel", kernel]
    if timeout is not None:
        cmd.extend(["--execution-timeout", str(timeout)])
    for key, value in (parameters or {}).items():
        cmd.extend(["-p", key, str(value)])

    start = os.times()
    try:
        proc = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=True,
        )
        end = os.times()
        return {
            "status": "ok",
            "input": input_path,
            "output": output_path,
            "parameters": parameters or {},
            "kernel": kernel,
            "elapsed_seconds": round((end.elapsed - start.elapsed), 2),
            "stdout": proc.stdout[-2000:] if proc.stdout else "",
        }
    except subprocess.CalledProcessError as e:
        end = os.times()
        return {
            "status": "error",
            "input": input_path,
            "output": output_path,
            "parameters": parameters or {},
            "kernel": kernel,
            "elapsed_seconds": round((end.elapsed - start.elapsed), 2),
            "stdout": e.stdout[-2000:] if e.stdout else "",
            "stderr": e.stderr[-2000:] if e.stderr else "",
            "returncode": e.returncode,
        }
    except FileNotFoundError:
        return {
            "status": "error",
            "message": "papermill not found. Install with: uv tool install papermill",
        }


def main(argv: List[str] = None) -> int:
    argv = argv or sys.argv[1:]
    parser = argparse.ArgumentParser(description="Notebook runner for agents")
    parser.add_argument("input", help="Input notebook path")
    parser.add_argument("output", nargs="?", default=None, help="Output notebook path")
    parser.add_argument("-p", "--parameter", action="append", default=[], help="Parameters as key=value")
    parser.add_argument("--kernel", default="python3")
    parser.add_argument("--timeout", type=int, default=None)
    parser.add_argument("--dry-run", action="store_true")
    args = parser.parse_args(argv)

    parameters = {}
    for raw in args.parameter:
        if "=" not in raw:
            print(f"Invalid parameter (expected key=value): {raw}", file=sys.stderr)
            return 1
        k, v = raw.split("=", 1)
        # Best-effort type inference
        if v.lower() in ("true", "false"):
            v = v.lower() == "true"
        else:
            try:
                v = int(v)
            except ValueError:
                try:
                    v = float(v)
                except ValueError:
                    pass
        parameters[k] = v

    result = run_notebook(
        args.input,
        args.output,
        parameters=parameters,
        kernel=args.kernel,
        timeout=args.timeout,
        dry_run=args.dry_run,
    )
    print(json.dumps(result, indent=2))
    return 0 if result.get("status") == "ok" else 1


if __name__ == "__main__":
    sys.exit(main())
