"""_race-Hedging: Stall-Slots bekommen einen parallelen Zwilling statt den Timeout-Cap
abzuwarten (gemessen: 4 Panel-Stalls à 160–230 s pro Lauf auf dem kritischen Pfad)."""

import asyncio

import pipeline


def _slot(payload=lambda r: r[1]):
    return {"key": "k1", "prompt": "p", "role": "judge", "capabilities": "none", "payload": payload}


async def test_hedge_zwilling_rettet_stall(monkeypatch):
    """Original stallt → nach HEDGE_NACH_S startet der Zwilling (key -h), sein Ergebnis
    gewinnt, das hängende Original wird gekillt."""
    calls, killed = [], []

    async def fake_agent(key, prompt, timeout, **kw):
        calls.append(key)
        if key.endswith("-h"):
            return (0, "zwilling", "")
        await asyncio.sleep(30)  # Stall — würde sonst den ganzen Cap verbrennen
        return (0, "original", "")

    monkeypatch.setattr(pipeline, "run_agent", fake_agent)
    monkeypatch.setattr(pipeline, "kill_process", lambda k: killed.append(k))
    monkeypatch.setattr(pipeline, "_HEDGE_NACH_S", 0.05)
    res = await pipeline._race("t", "Test", [_slot()], 1, 0.1, "claude")
    assert res == ["zwilling"]
    assert calls == ["k1", "k1-h"]
    assert "k1" in killed  # das hängende Original läuft nicht weiter


async def test_hedge_original_gewinnt_zwilling_wird_gekillt(monkeypatch):
    """Kommt das Original doch noch vor dem Zwilling an, wird der Zwilling gekillt
    und sein spätes Ergebnis nicht gewertet."""
    killed = []

    async def fake_agent(key, prompt, timeout, **kw):
        await asyncio.sleep(0.3 if key.endswith("-h") else 0.15)
        return (0, key, "")

    monkeypatch.setattr(pipeline, "run_agent", fake_agent)
    monkeypatch.setattr(pipeline, "kill_process", lambda k: killed.append(k))
    monkeypatch.setattr(pipeline, "_HEDGE_NACH_S", 0.05)
    res = await pipeline._race("t", "Test", [_slot()], 1, 0.1, "claude")
    assert res == ["k1"]
    assert "k1-h" in killed


async def test_hedge_aus_bei_null(monkeypatch):
    """HEDGE_NACH_S=0 → kein Zwilling, Verhalten wie zuvor."""
    calls = []

    async def fake_agent(key, prompt, timeout, **kw):
        calls.append(key)
        await asyncio.sleep(0.1)
        return (0, "ok", "")

    monkeypatch.setattr(pipeline, "run_agent", fake_agent)
    monkeypatch.setattr(pipeline, "_HEDGE_NACH_S", 0)
    res = await pipeline._race("t", "Test", [_slot()], 1, 0.1, "claude")
    assert res == ["ok"]
    assert calls == ["k1"]


async def test_hedge_schwelle_skaliert_mit_timeout(monkeypatch):
    """Die Schwelle ist max(HEDGE_NACH_S, timeout/2): ein gesunder Call, der länger als
    die Untergrenze, aber kürzer als das halbe Timeout läuft, bekommt KEINEN Zwilling
    (pauschale 90 s hedgten jeden normalen Fix-Call)."""
    calls = []

    async def fake_agent(key, prompt, timeout, **kw):
        calls.append(key)
        await asyncio.sleep(0.15)  # > Untergrenze 0.05, < timeout/2 = 0.5
        return (0, "ok", "")

    monkeypatch.setattr(pipeline, "run_agent", fake_agent)
    monkeypatch.setattr(pipeline, "_HEDGE_NACH_S", 0.05)
    res = await pipeline._race("t", "Test", [_slot()], 1, 1.0, "claude")
    assert res == ["ok"]
    assert calls == ["k1"]


async def test_hedge_zwilling_ersetzt_restart(monkeypatch):
    """Scheitert das Original, während der Zwilling noch läuft, gibt es KEINEN
    zusätzlichen Restart — der Zwilling ist der Retry."""
    calls = []

    async def fake_agent(key, prompt, timeout, **kw):
        calls.append(key)
        if key.endswith("-h"):
            await asyncio.sleep(0.2)
            return (0, "zwilling", "")
        await asyncio.sleep(0.1)
        return (1, "", "kaputt")  # Fehler NACH dem Hedge-Start

    monkeypatch.setattr(pipeline, "run_agent", fake_agent)
    monkeypatch.setattr(pipeline, "kill_process", lambda k: None)
    monkeypatch.setattr(pipeline, "_HEDGE_NACH_S", 0.05)
    res = await pipeline._race("t", "Test", [_slot()], 1, 0.1, "claude")
    assert res == ["zwilling"]
    assert calls == ["k1", "k1-h"]  # kein dritter Spawn
