"""soup data forge — Synthetic Data Forge CLI (v0.47.0 Part A / v0.53.7 #111)."""

from __future__ import annotations

from typing import Optional

import typer
from rich.console import Console
from rich.markup import escape
from rich.panel import Panel

console = Console()


def _default_judge(prompt: str) -> dict:
    """Deterministic offline judge used when no provider is configured.

    Live judge integration (Ollama / Anthropic / vLLM via v0.20.0
    providers) is wired through ``--judge-provider`` in v0.53.7 #111.
    """
    head = prompt.splitlines()[0] if prompt else ""
    return {"text": f"Synthesised answer (offline): {head[:80]}"}


def forge(
    docs: str = typer.Option(
        ..., "--docs", "-d",
        help="Directory of documents (txt/md/json/jsonl, under cwd).",
    ),
    task: str = typer.Option(
        "sft", "--task", "-t",
        help="Forge task: sft | preference | tool.",
    ),
    target_rows: int = typer.Option(
        100, "--target-rows", "-r",
        min=1, max=1_000_000,
        help="Maximum number of synthesised rows.",
    ),
    teacher: str = typer.Option(
        "local-judge", "--teacher",
        help="Label recorded in provenance for the judge backend.",
    ),
    output: str = typer.Option(
        "forge_dataset.jsonl", "--output", "-o",
        help="JSONL output path (under cwd).",
    ),
    provenance: str = typer.Option(
        "forge_provenance.json", "--provenance",
        help="Provenance manifest output path (under cwd).",
    ),
    uncertainty_threshold: float = typer.Option(
        0.0, "--uncertainty-threshold",
        min=0.0, max=1.0,
        help="Minimum Jaccard-distance score required to keep a synthesised row.",
    ),
    max_chunk_chars: int = typer.Option(
        1000, "--max-chunk-chars",
        min=1, max=64_000,
        help="Maximum chars per document chunk before judge call.",
    ),
    judge_provider: Optional[str] = typer.Option(
        None, "--judge-provider",
        help=(
            "v0.53.7 #111: live judge backend. One of "
            "ollama / anthropic / vllm. Omit for the deterministic "
            "offline stub."
        ),
    ),
    judge_model: str = typer.Option(
        "llama3.1", "--judge-model",
        help="Model name for the live judge provider (default llama3.1).",
    ),
    judge_base_url: Optional[str] = typer.Option(
        None, "--judge-base-url",
        help=(
            "Override base URL for Ollama (localhost-only) / vLLM "
            "(scheme allowlist + loopback). Ignored for Anthropic."
        ),
    ),
):
    """Run the multi-stage synthetic data pipeline with provenance.

    v0.53.7 #111: ``--judge-provider {ollama, anthropic, vllm}`` swaps the
    offline echo stub for a real judge backend. Ollama is localhost-only,
    Anthropic reads ``ANTHROPIC_API_KEY`` from env, vLLM is
    scheme-allowlist + loopback validated.
    """
    from soup_cli.utils.data_forge import (
        JUDGE_PROVIDERS,
        build_forge_plan,
        discover_documents,
        make_judge_provider_fn,
        synthesise_forge_rows,
        write_forge_dataset,
        write_provenance,
    )

    judge_fn = _default_judge
    effective_teacher = teacher
    if judge_provider is not None:
        canonical = judge_provider.strip().lower()
        if canonical not in JUDGE_PROVIDERS:
            console.print(
                f"[red]Unknown --judge-provider:[/] {escape(judge_provider)}. "
                f"Choose from {sorted(JUDGE_PROVIDERS)}."
            )
            raise typer.Exit(2)
        try:
            judge_fn = make_judge_provider_fn(
                canonical,
                model=judge_model,
                base_url=judge_base_url,
            )
        except (TypeError, ValueError, ImportError) as exc:
            console.print(
                f"[red]Failed to build judge backend:[/] {escape(str(exc))}"
            )
            raise typer.Exit(1) from exc
        # Record the live backend in provenance.
        if teacher == "local-judge":
            effective_teacher = f"{canonical}:{judge_model}"

    try:
        plan = build_forge_plan(
            docs_dir=docs,
            task=task,
            target_rows=target_rows,
            teacher=effective_teacher,
            uncertainty_threshold=uncertainty_threshold,
        )
        doc_paths = discover_documents(docs)
    except (TypeError, ValueError, FileNotFoundError) as exc:
        console.print(f"[red]Plan failed:[/] {escape(str(exc))}")
        raise typer.Exit(1) from exc

    rows = synthesise_forge_rows(
        doc_paths,
        task=plan.task,
        target_rows=plan.target_rows,
        judge=judge_fn,
        teacher=plan.teacher,
        uncertainty_threshold=plan.uncertainty_threshold,
        max_chunk_chars=max_chunk_chars,
    )

    if not rows:
        console.print(
            "[yellow]No rows produced.[/] Try a lower --uncertainty-threshold "
            "or check your --docs directory."
        )
        raise typer.Exit(1)

    try:
        dataset_path = write_forge_dataset(rows, output)
        manifest_path = write_provenance(rows, provenance)
    except (TypeError, ValueError) as exc:
        console.print(f"[red]Write failed:[/] {escape(str(exc))}")
        raise typer.Exit(1) from exc

    console.print(
        Panel(
            f"Task:        [bold]{escape(plan.task)}[/]\n"
            f"Docs scanned:[bold] {plan.num_docs}[/]\n"
            f"Rows kept:   [bold]{len(rows)}[/] / target {plan.target_rows}\n"
            f"Teacher:     [bold]{escape(plan.teacher)}[/]\n"
            f"Threshold:   [bold]{plan.uncertainty_threshold:.2f}[/]\n"
            f"Dataset:     [bold]{escape(dataset_path)}[/]\n"
            f"Provenance:  [bold]{escape(manifest_path)}[/]",
            title="[bold green]Data Forge — synth complete[/]",
        )
    )
    if judge_provider is None:
        console.print(
            "[dim]The built-in judge is the deterministic offline stub. "
            "Pass --judge-provider {ollama, anthropic, vllm} to wire a "
            "live backend (v0.53.7 #111).[/]"
        )
    else:
        console.print(
            f"[dim]Live judge backend: [bold]{escape(judge_provider)}[/] "
            f"(model: {escape(judge_model)}).[/]"
        )
