"""Stop a run in which nothing is succeeding.

An expired key, an exhausted quota or a model id the provider does not serve
fails every trial identically. Without this the queue ran to completion: in the
session that prompted it, eighteen trials all failed "Not logged in", the whole
design was paid for, and the failure arrived as a report rather than an error.
"""

from __future__ import annotations

import time
from pathlib import Path

from ablate.record import AssertionRecord, TelemetryRecord, TrialRecord
from ablate.runner import schedule
from ablate.runner.schedule import FAILFAST_THRESHOLD, run_jobs
from ablate.runner.trial import TrialJob
from ablate.task import Task


def _task(i: int) -> Task:
    return Task(
        id=f"task-{i}",
        prompt="do the thing",
        assertions=[{"id": "a", "type": "exit_code", "run": "true"}],
    )


def _jobs(n: int) -> list[TrialJob]:
    from ablate.config.variant import ConfigVariant

    variant = ConfigVariant(id="A", source="CLAUDE.md", text="# rules\n")
    return [TrialJob(task=_task(i), variant=variant, replicate=1) for i in range(n)]


def _record(job: TrialJob, *, error: str | None) -> TrialRecord:
    return TrialRecord(
        trial_id=job.trial_id,
        run_id="run1",
        task_id=job.task.id,
        variant_id=job.variant.id,
        variant_hash="sha256:x",
        replicate=job.replicate,
        agent="claude-code",
        started_at="2026-07-28T00:00:00Z",
        duration_s=0.1,
        telemetry=TelemetryRecord(turns=0, cost_usd=None),
        assertions=[AssertionRecord(id="a", type="exit_code", passed=error is None)],
        passed=error is None,
        error=error,
    )


def _patch_runner(monkeypatch, outcomes: dict[str, str | None], *, started: list | None = None):
    """Replace run_trial so no git worktree or agent is involved.

    The small sleep is load-bearing. A trial that returns instantly lets the
    whole pool drain before `as_completed` yields its first result, so nothing
    is left to cancel and the test would pass against a no-op implementation.
    Real trials take ~75s; this only has to be slower than the scheduler.
    """

    def fake_run_trial(job, **kwargs):  # noqa: ANN001, ANN003
        if started is not None:
            started.append(job.trial_id)
        time.sleep(0.01)
        return _record(job, error=outcomes.get(job.task.id, "boom"))

    monkeypatch.setattr(schedule, "run_trial", fake_run_trial)


def _run(jobs, **kwargs):  # noqa: ANN001, ANN003
    return run_jobs(
        jobs=jobs,
        repo=Path("/nonexistent"),
        worktrees_root=Path("/nonexistent"),
        adapter=object(),
        run_id="run1",
        concurrency=1,
        **kwargs,
    )


def test_universal_failure_aborts_before_the_whole_queue_runs(monkeypatch):
    started: list[str] = []
    _patch_runner(monkeypatch, {}, started=started)
    jobs = _jobs(30)
    reasons: list[str] = []

    _, stopped = _run(jobs, on_abort=reasons.append)

    assert stopped is True
    assert reasons
    # The point: most of the budget is never spent. Exactly how many trials are
    # in flight when the abort fires depends on the pool, so this asserts the
    # guarantee rather than a scheduling detail.
    assert len(started) < len(jobs)
    assert len(started) >= FAILFAST_THRESHOLD


def test_abort_reason_quotes_the_agents_own_error(monkeypatch):
    """The useful diagnostic is the agent's message, not one we compose."""
    _patch_runner(monkeypatch, {})
    monkeypatch.setattr(
        schedule,
        "run_trial",
        lambda job, **kw: _record(job, error='{"is_error":true,"result":"Not logged in"}'),
    )
    reasons: list[str] = []

    _run(_jobs(30), on_abort=reasons.append)

    assert "Not logged in" in reasons[0]


def test_one_success_disables_failfast(monkeypatch):
    """Once the setup is proven, later failures are data about the tasks.

    A hard suite where most trials legitimately fail must still run to
    completion — that is a measurement, not a misconfiguration.
    """
    outcomes = {f"task-{i}": "boom" for i in range(30)}
    outcomes["task-0"] = None
    _patch_runner(monkeypatch, outcomes)
    jobs = _jobs(30)
    reasons: list[str] = []

    records, stopped = _run(jobs, on_abort=reasons.append)

    assert not reasons
    assert stopped is False
    assert len(records) == len(jobs)


def test_failures_below_the_threshold_do_not_abort(monkeypatch):
    outcomes = {f"task-{i}": None for i in range(30)}
    for i in range(FAILFAST_THRESHOLD - 1):
        outcomes[f"task-{i}"] = "boom"
    _patch_runner(monkeypatch, outcomes)
    reasons: list[str] = []

    records, stopped = _run(_jobs(30), on_abort=reasons.append)

    assert not reasons
    assert stopped is False
    assert len(records) == 30
