"""Job discovery and bundle generation with CLI entrypoint.

This module wires demo_manifest.py to real repo filesystem paths,
providing job discovery, manifest generation, and persistence.
"""

from __future__ import annotations

import argparse
import json
import operator
import sys
from pathlib import Path

from ._helpers import discover_repo_root_from_cwd
from .demo_manifest import GsdContextLink, ReplayBundle, build_manifest, save_manifest


def discover_jobs(jobs_root: Path) -> list[tuple[str, str, Path]]:
    """Walk jobs_root/{vm_name}/{job_id}.json and return sorted tuples.

    Args:
        jobs_root: Root directory containing job files (e.g., .mcp-vm-blackbox/jobs/)

    Returns:
        List of (vm_name, job_id, job_path) tuples sorted by vm_name then job_id
    """
    if not jobs_root.exists():
        return []

    jobs: list[tuple[str, str, Path]] = []
    for vm_dir in jobs_root.iterdir():
        if not vm_dir.is_dir():
            continue
        vm_name = vm_dir.name
        for job_file in vm_dir.glob("*.json"):
            job_id = job_file.stem
            jobs.append((vm_name, job_id, job_file))

    # Sort by vm_name, then job_id
    return sorted(jobs, key=operator.itemgetter(0, 1))


def generate_bundle(
    vm_name: str, job_id: str, repo_root: Path, gsd_context: list[GsdContextLink] | None = None
) -> ReplayBundle:
    """Generate a ReplayBundle for a specific job.

    Resolves paths to job file, screenshots dir, recordings dir, and builds manifest.

    Args:
        vm_name: VM name (directory name in jobs_root)
        job_id: Job ID (JSON filename without extension)
        repo_root: Repository root path
        gsd_context: Optional .gsd context links

    Returns:
        ReplayBundle with truthful present/missing evidence

    Raises:
        FileNotFoundError: If job file does not exist
        json.JSONDecodeError: If job file is not valid JSON
    """
    jobs_root = repo_root / ".mcp-vm-blackbox" / "jobs"
    job_path = jobs_root / vm_name / f"{job_id}.json"

    if not job_path.exists():
        raise FileNotFoundError(f"Job file not found: {job_path}")

    # Validate job file is readable JSON
    job_data = json.loads(job_path.read_text(encoding="utf-8"))

    # Use vm_name from job file if available (for consistency)
    actual_vm_name = job_data.get("vm_name", vm_name)

    # Resolve evidence directories
    screenshots_dir = repo_root / "scratch" / "screenshots"
    recordings_dir = repo_root / "scratch" / "recordings"

    return build_manifest(
        job_id=job_id,
        vm_name=actual_vm_name,
        job_path=job_path,
        screenshots_dir=screenshots_dir,
        recordings_dir=recordings_dir if recordings_dir.exists() else None,
        gsd_context=gsd_context,
        repo_root=repo_root,
    )


def _cli_list(jobs_root: Path) -> int:
    """CLI: List available jobs."""
    jobs = discover_jobs(jobs_root)
    if not jobs:
        print("No jobs found.", file=sys.stderr)
        return 1

    print(f"Found {len(jobs)} jobs:\n")
    for vm_name, job_id, job_path in jobs:
        print(f"  {vm_name}/{job_id}  ({job_path})")
    return 0


def _cli_generate(vm_name: str, job_id: str, repo_root: Path, output_path: Path | None) -> int:
    """CLI: Generate and persist manifest."""
    try:
        bundle = generate_bundle(vm_name, job_id, repo_root)
    except FileNotFoundError as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1
    except json.JSONDecodeError as e:
        print(f"Error: Invalid job JSON: {e}", file=sys.stderr)
        return 1

    # Default output path
    if output_path is None:
        output_path = repo_root / ".mcp-vm-blackbox" / "manifests" / vm_name / f"{job_id}.json"

    save_manifest(bundle, output_path)
    print(f"Manifest written to: {output_path}")
    print(f"  Bundle ID: {bundle.bundle_id}")
    print(f"  Evidence items: {len(bundle.evidence)}")
    print(f"  Source evidence present: {bundle.source_evidence_count}")
    print(f"  Has continuous motion: {bundle.has_continuous_motion}")
    if bundle.missing_evidence_summary:
        print(f"  Missing evidence: {bundle.missing_evidence_summary}")
    return 0


def main(argv: list[str] | None = None) -> int:
    """CLI entrypoint."""
    parser = argparse.ArgumentParser(prog="demo_bundle", description="Generate replay bundles from VM job evidence")
    parser.add_argument("--list", action="store_true", help="List available jobs")
    parser.add_argument("--vm", metavar="NAME", help="VM name for manifest generation")
    parser.add_argument("--job", metavar="ID", help="Job ID for manifest generation")
    parser.add_argument(
        "--output",
        "-o",
        metavar="PATH",
        help="Output path for manifest JSON (default: .mcp-vm-blackbox/manifests/{vm}/{job}.json)",
    )
    parser.add_argument("--repo-root", metavar="PATH", help="Repository root (default: auto-detect from cwd)")

    args = parser.parse_args(argv)

    # Determine repo root
    repo_root = Path(args.repo_root).resolve() if args.repo_root else (discover_repo_root_from_cwd() or Path.cwd())

    jobs_root = repo_root / ".mcp-vm-blackbox" / "jobs"

    # Handle --list
    if args.list:
        return _cli_list(jobs_root)

    # Handle --vm + --job
    if args.vm and args.job:
        output_path = Path(args.output) if args.output else None
        return _cli_generate(args.vm, args.job, repo_root, output_path)

    # Missing required args for generation
    if args.vm or args.job:
        print("Error: Both --vm and --job are required for manifest generation", file=sys.stderr)
        return 1

    # No action specified
    parser.print_help()
    return 0


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