"""The terminal report (docs/08-surfaces.md).

This is the artifact people screenshot, so the layout is specified as tightly
as the statistics. Rules encoded here, each of them load-bearing:

- Every headline number carries an interval, or an explicit statement of why it
  has none. There is no bare "not significant" anywhere in this file.
- The four verdicts get four distinct colours, and EQUIVALENT is deliberately
  not grey — it is a finding, not an absence, and it is the finding that
  licenses deletion.
- Per-task rows are labelled descriptive and carry no inferential decoration.
  Picking the largest of twenty noisy per-task deltas and calling it a result
  is the classic failure; the report makes it structurally impossible.
- Every EQUIVALENT or INCONCLUSIVE verdict ends with the price of a sharper
  answer, which turns "the tool failed" into "the tool told me what an answer
  costs."
"""

from __future__ import annotations

from rich.console import Console

from ablate.config.tokens import ACCURACY, estimate_tokens
from ablate.config.variant import ConfigVariant
from ablate.cost import ModelPrice, config_cost_per_turn
from ablate.report.analysis import Analysis, MetricAnalysis
from ablate.report.formats import NOT_REPORTED, fmt_arm
from ablate.stats import Verdict, describe

BAR_WIDTH = 38

#: Everything is laid out inside this, and it tracks the terminal so the
#: horizontal rules and the verdict column never wrap. A report that wraps is
#: a report nobody screenshots.
MAX_WIDTH = 84
MIN_WIDTH = 60


def _width(console: Console) -> int:
    return max(MIN_WIDTH, min(console.width, MAX_WIDTH))


def _rule(console: Console) -> str:
    return "─" * _width(console)


def _bar(value: float, ceiling: float, colour: str) -> str:
    if ceiling <= 0:
        filled = 0
    else:
        filled = max(0, min(BAR_WIDTH, round(BAR_WIDTH * value / ceiling)))
    return f"[{colour}]{'█' * filled}[/{colour}][dim]{'░' * (BAR_WIDTH - filled)}[/dim]"


def _zeroed(value: float, epsilon: float = 5e-5) -> float:
    """Collapse values that round to zero, so nothing ever prints as "-0.0pp"."""
    return 0.0 if abs(value) < epsilon else value


def _signed_pp(value: float) -> str:
    return f"{_zeroed(value) * 100:+.1f}pp"


def _signed_pct(value: float) -> str:
    return f"{_zeroed(value) * 100:+.1f}%"


def _interval(metric: MetricAnalysis) -> str:
    v = metric.verdict
    fmt = _signed_pct if metric.spec.relative else _signed_pp
    if not v.has_ci:
        return "[yellow]no interval[/yellow]"
    return f"[{fmt(v.lo)}, {fmt(v.hi)}]"


def _render_metric(
    console: Console,
    metric: MetricAnalysis,
    variant_ids: tuple[str, ...],
    *,
    show_method: bool = False,
) -> None:
    spec, verdict = metric.spec, metric.verdict
    colour = verdict.verdict.colour

    console.print()
    console.print(
        f"  [bold]{spec.label}[/bold]"
        f"{' ' * max(1, _width(console) - len(spec.label) - len(verdict.verdict.value) - 6)}"
        f"[{colour}]{verdict.verdict.glyph} {verdict.verdict.value}[/{colour}]"
    )
    console.print()

    values = [metric.arm_values[v] for v in variant_ids]
    known = [v for v in values if v is not None]
    ceiling = (max(known) if known else 1.0) if spec.key != "pass_rate" else 1.0
    for variant_id in variant_ids:
        value = metric.arm_values[variant_id]
        # An arm that reported nothing gets no bar. A full-width empty bar
        # beside "0.00" reads as a measured floor rather than an absence.
        bar = _bar(value, ceiling, colour) if value is not None else ""
        console.print(
            f"      [bold]{variant_id}[/bold]  {fmt_arm(metric, variant_id):>9}   {bar}"
        )

    console.print()

    if not metric.measured:
        delta = f"Δ = {NOT_REPORTED}"
    elif spec.relative:
        # Rendered with the metric's own formatter: "+0.2" for turns,
        # "+4,594" for tokens. A shared integer format would print a
        # two-tenths-of-a-turn difference as "-0".
        absolute = _zeroed(values[0] - values[1], epsilon=0.005)
        sign = "+" if absolute >= 0 else "-"
        delta = f"Δ = {sign}{spec.fmt(abs(absolute))} ({_signed_pct(verdict.delta)})"
    else:
        delta = f"Δ = {_signed_pp(verdict.delta)}"

    confidence = f"{(1 - verdict.result.alpha) * 100:.0f}% CI"
    line = f"      {delta}   {confidence} {_interval(metric)}"
    # The method is identical for every metric, so it is stated once rather
    # than wrapped onto three separate lines.
    if verdict.has_ci and show_method:
        line += (
            f"   [dim]{verdict.result.method.replace('paired cluster bootstrap, ', '')}, "
            f"{verdict.result.n_resamples // 1000}k resamples[/dim]"
        )
    console.print(line)
    console.print()

    for chunk in _wrap(describe(verdict), width=_width(console) - 8):
        console.print(f"      [{colour}]{chunk}[/{colour}]")


def _wrap(text: str, width: int) -> list[str]:
    words, lines, current = text.split(), [], ""
    for word in words:
        candidate = f"{current} {word}".strip()
        if len(candidate) > width and current:
            lines.append(current)
            current = word
        else:
            current = candidate
    if current:
        lines.append(current)
    return lines


def _render_tasks(console: Console, analysis: Analysis) -> None:
    console.print()
    console.print(
        "  [bold]PER TASK[/bold]"
        f"{' ' * 21}[dim](descriptive only — no per-task tests)[/dim]"
    )
    console.print()

    a, b = analysis.variant_ids
    width = max((len(t.task_id) for t in analysis.tasks), default=4) + 2
    console.print(
        f"    [dim]{'task'.ljust(width)}{a.rjust(7)}{b.rjust(8)}{'Δ'.rjust(9)}[/dim]"
    )

    for row in analysis.tasks:
        cells = [row.task_id.ljust(width)]
        for variant_id in (a, b):
            total = row.total.get(variant_id, 0)
            cells.append(
                (f"{row.passed[variant_id]}/{total}" if total else "—").rjust(
                    7 if variant_id == a else 8
                )
            )
        cells.append(("—" if row.delta is None else _signed_pp(row.delta)).rjust(9))

        line = f"    {''.join(cells)}"
        if row.degenerate:
            line += f"   [dim]⚠ no signal — {row.degenerate}[/dim]"
        console.print(line)


def _render_verdict(
    console: Console,
    analysis: Analysis,
    variants: list[ConfigVariant],
    config_tokens: int,
    price: ModelPrice | None,
) -> None:
    console.print()
    console.print("  [bold]VERDICT[/bold]")
    console.print()

    pass_rate = analysis.metric("pass_rate")
    tokens = analysis.metric("tokens_per_turn")
    config_a = variants[0]
    name = config_a.source if config_a.text is not None else "no config"

    cost_clause = ""
    if config_tokens and price is not None:
        # Quoted per 1,000 turns. Per *turn* the number rounds to $0.0000 for
        # any realistic config, which reads as "free" — the opposite of the
        # point. A config is re-sent on every turn of every session forever,
        # so the meaningful unit is the accumulation, not the increment.
        per_thousand = config_cost_per_turn(config_tokens, price) * 1000
        cost_clause = (
            f"Your {config_a.line_count}-line {name} costs ~{config_tokens:,} tokens "
            f"on every turn (~${per_thousand:.2f} per 1,000 turns at {price.label} rates)"
        )
    elif config_tokens:
        cost_clause = (
            f"Your {config_a.line_count}-line {name} costs ~{config_tokens:,} tokens "
            f"on every turn"
        )

    sentences: list[str] = []
    if pass_rate is not None:
        v = pass_rate.verdict
        if v.verdict is Verdict.EQUIVALENT:
            margin = f"±{v.margin * 100:.0f}pp"
            tail = (
                f"and does not measurably change whether tasks pass ({margin}, "
                f"{(1 - v.result.alpha) * 100:.0f}% confidence)."
            )
            sentences.append(f"{cost_clause} {tail}" if cost_clause else tail.capitalize())
            sentences.append(
                "This does NOT mean it has no effect — it means any effect is smaller "
                "than this suite can resolve."
            )
        elif v.verdict is Verdict.INCONCLUSIVE:
            if cost_clause:
                sentences.append(f"{cost_clause}.")
            sentences.append(
                "Whether it changes pass rate is unresolved at this suite size: "
                f"{describe(v)}"
            )
        else:
            if cost_clause:
                sentences.append(f"{cost_clause}.")
            sentences.append(describe(v, label_a=name))

    if tokens is not None and tokens.verdict.verdict.is_decisive:
        sentences.append(describe(tokens.verdict, label_a=name))

    for sentence in sentences:
        for chunk in _wrap(sentence, width=_width(console) - 6):
            console.print(f"    {chunk}")
        console.print()

    for warning in analysis.warnings:
        for chunk in _wrap(f"⚠ {warning}", width=_width(console) - 6):
            console.print(f"    [yellow]{chunk}[/yellow]")
        console.print()


def render_report(
    console: Console,
    analysis: Analysis,
    variants: list[ConfigVariant],
    *,
    command: str,
    version: str = "",
    price: ModelPrice | None = None,
    run_path: str | None = None,
    partial: str | None = None,
    analyses: int = 1,
) -> None:
    """Render the full comparison report."""
    variant_ids = analysis.variant_ids

    console.print()
    if command:
        console.print(f"  [dim]$ {command}[/dim]")

    header = [f"ablate {version}" if version else "ablate"]
    if analysis.agent:
        header.append(
            f"{analysis.agent} {analysis.agent_version}"
            if analysis.agent_version
            else analysis.agent
        )
    header.extend(analysis.models)
    header.append(
        f"{analysis.n_tasks} tasks × {analysis.n_trials} trials × "
        f"{len(variant_ids)} arms"
    )
    console.print(f"  [dim]{' · '.join(header)}[/dim]")
    console.print()

    config_tokens = 0
    for variant in variants:
        tokens = estimate_tokens(variant.text)
        if variant.id == variant_ids[0]:
            config_tokens = tokens
        label = variant.source if variant.text is not None else "(no config)"
        console.print(
            f"  [bold]{variant.id}[/bold]  {label:<24}"
            f"{variant.line_count:>5} lines · {tokens:>7,} tokens/turn"
        )

    if any(v.text for v in variants):
        console.print(
            f"  [dim]{' ' * 30}token counts are estimates, ±{ACCURACY * 100:.0f}%[/dim]"
        )

    if partial:
        console.print()
        console.print(f"  [yellow]PARTIAL — {partial}[/yellow]")

    console.print()
    console.print(f"[dim]{_rule(console)}[/dim]")

    for n, metric in enumerate(analysis.metrics):
        _render_metric(console, metric, variant_ids, show_method=(n == 0))

    console.print()
    console.print(f"[dim]{_rule(console)}[/dim]")
    _render_tasks(console, analysis)

    console.print()
    console.print(f"[dim]{_rule(console)}[/dim]")
    _render_verdict(console, analysis, variants, config_tokens, price)

    footer = []
    if analysis.run_id:
        footer.append(f"Run {analysis.run_id}")
    footer.append(f"{analysis.n_records} trials")
    if analysis.cost_known and analysis.total_cost_usd:
        footer.append(f"${analysis.total_cost_usd:.2f}")
    if analysis.steady_state_cost_usd is not None:
        # The first trial of each cell pays a prompt-cache write; the rest hit a
        # warm cache. Quoting only the blended mean overstates what the next run
        # costs, which is the number anyone is actually budgeting with (Q7).
        footer.append(f"${analysis.steady_state_cost_usd:.2f}/trial warm")
    footer.append(f"α = {analysis.alpha}")
    if analysis.seed is not None:
        footer.append(f"seed {analysis.seed}")
    console.print(f"  [dim]{' · '.join(footer)}[/dim]")

    def _footnote(text: str, style: str = "dim") -> None:
        for chunk in _wrap(text, width=_width(console) - 4):
            console.print(f"  [{style}]{chunk}[/{style}]")

    # Stated once, in full. This and the per-metric intervals are what make the
    # report something a skeptical reader can check rather than argue with.
    _footnote(
        "Method: paired cluster bootstrap (BCa), pairing at task level, "
        "resampling tasks then trials within arm. Δ is an average over this "
        "suite, for this agent at this model version."
    )
    _footnote(
        "ablate report --last to re-analyze for free "
        "(different margin, different α — zero API calls)"
    )
    if analyses > 1:
        # Re-analysis is free, which makes it easy to keep adjusting the margin
        # until the verdict is agreeable — sequential testing with no
        # correction. The tool cannot stop that; it declines to hide it.
        _footnote(
            f"Analyses run against this dataset: {analyses}. Verdicts chosen "
            "after seeing the data are not protected by the stated α.",
            style="yellow",
        )
    if run_path:
        console.print(f"  [dim]raw log: {run_path}[/dim]")
    console.print()
