"""Fake-Welt: deterministische Agenten-Antworten ohne LLM (PLANER_FAKE_AGENTS=1).

Creator-Muster: eine Welt beantwortet Calls anhand von agent_key-Mustern und kann
Störungen einspielen ({muster, modus, mal}) — damit testet die Fake-E2E den kompletten
Pfad inkl. Retry/Dead-Letter/Pause in Sekunden.
"""
import re

# Modul-globale Welt für den ENV-Weg; Tests setzen ihre eigene via set_welt().
_WELT = None


def set_welt(welt) -> None:
    global _WELT
    _WELT = welt


class Welt:
    def __init__(self):
        self.calls: list[str] = []          # Auditspur aller agent_keys
        self.stoerungen: list[dict] = []    # {muster, modus: fehler|garbage|infra, mal, [antwort]}
        self.antworten: list[tuple[str, object]] = []  # (regex auf agent_key, fn(prompt) -> str)

    def antwort_auf(self, muster: str, fn) -> None:
        self.antworten.append((muster, fn))

    def _stoerung(self, agent_key: str) -> tuple[int, str, str] | None:
        for s in self.stoerungen:
            if s.get("mal", 0) > 0 and re.search(s["muster"], agent_key):
                s["mal"] -= 1
                modus = s.get("modus", "fehler")
                if modus == "fehler":
                    return 1, "", "fake-stoerung"
                if modus == "garbage":
                    return 0, "### kaputtes {fragment", ""
                if modus == "infra":
                    from agents import AgentInfraError
                    raise AgentInfraError("fake-infra-stoerung")
                if modus == "antwort":
                    return 0, s.get("antwort", ""), ""
        return None

    async def respond(self, agent_key: str, prompt: str, capabilities: str) -> tuple[int, str, str]:
        self.calls.append(agent_key)
        gestoert = self._stoerung(agent_key)
        if gestoert is not None:
            return gestoert
        for muster, fn in self.antworten:
            if re.search(muster, agent_key):
                return 0, fn(prompt), ""
        return 1, "", f"fake: keine Antwort für {agent_key}"


async def respond(agent_key: str, prompt: str, capabilities: str) -> tuple[int, str, str]:
    global _WELT
    if _WELT is None:
        _WELT = Welt()
        standard_antworten(_WELT)
    return await _WELT.respond(agent_key, prompt, capabilities)


# --- Standard-Antworten: mechanisch erzeugte Ausgaben, die die ECHTEN Gates bestehen ---

def _quelle_aus(prompt: str) -> tuple[str, str]:
    """(datei, quelltext) aus einem Scan-Prompt (Template-Struktur)."""
    m = re.search(r"Ausschnitt aus `(.+?)`\n\n```python\n(.*?)\n?```", prompt, re.S)
    return (m.group(1), m.group(2)) if m else ("", "")


def _symbol_liste(prompt: str, kopf: str) -> list[str]:
    """`- symbol`-Liste direkt nach der Zeile mit `kopf` (Leerzeilen dazwischen erlaubt)."""
    idx = prompt.find(kopf)
    if idx < 0:
        return []
    out: list[str] = []
    for zeile in prompt[idx:].splitlines()[1:]:
        z = zeile.strip()
        if z.startswith("- "):
            if "(keine)" not in z:
                out.append(z[2:].strip())
        elif z:
            break  # erste andere Zeile beendet die Liste
    return out


def _einheit_fuer(datei: str, symbol: str, quelle: str) -> str:
    """Eine gültige Einheit: Beleg = die wörtliche Definitionszeile aus der Quelle."""
    beleg = next((z.strip() for z in quelle.splitlines()
                  if re.match(rf"\s*(?:async\s+)?(?:def|class)\s+{re.escape(symbol)}\b", z)
                  or re.match(rf"{re.escape(symbol)}\s*[:=]", z)), "")
    beleg = beleg[:60].replace('"', "'")
    return (f"## {datei}::{symbol}\n"
            f"beschreibung: Fake-Verantwortung von {symbol}.\n"
            f"input: Fake-Input\noutput: Fake-Output\n"
            f"entscheidungen:\n- Fake-Entscheidung zu {symbol}.\n  beleg: \"{beleg}\"\n"
            f"kanten:\n")


def _fake_schneiden(prompt: str, lasse_aus: set[str]) -> str:
    datei, quelle = _quelle_aus(prompt)
    symbole = [s for s in _symbol_liste(prompt, "MÜSSEN alle abgedeckt sein") if s not in lasse_aus]
    if not symbole:  # Datei ohne def/class: erste Konstante als Einheit
        m = re.search(r"^(\w+)\s*[:=]", quelle, re.M)
        symbole = [m.group(1)] if m else []
    return "\n".join(_einheit_fuer(datei, s, quelle) for s in symbole)


def _fake_nachfass(prompt: str) -> str:
    datei, quelle = _quelle_aus(prompt)
    fehlend = _symbol_liste(prompt, "zugeordnet sind:")
    return "\n".join(_einheit_fuer(datei, s, quelle) for s in fehlend)


def _ids_aus_uebersicht(prompt: str) -> list[str]:
    return re.findall(r"^- (\S+::\S+):", prompt, re.M)


def _projekt_aus(prompt: str) -> str:
    m = re.search(r"Einheiten von `(.+?)`", prompt)
    return m.group(1) if m else "projekt"


def _fake_features(prompt: str) -> str:
    ids = _ids_aus_uebersicht(prompt)
    p = _projekt_aus(prompt)
    return (f"# Kern: {p}\nFake-Kern in zwei Sätzen. Mehr Fake.\n\n"
            f"tragende-einheiten: {ids[0]}\n\n"
            f"# Bereich: fake-bereich\nbeschreibung: Ein Fake-Bereich.\n\n"
            f"## Feature: Fake-Feature [kern]\nbeschreibung: Kann alles Fake.\n\n"
            f"### Kann fake arbeiten\neinheiten: {', '.join(ids)}\n")


def _fake_architektur(prompt: str) -> str:
    ids = _ids_aus_uebersicht(prompt)
    p = _projekt_aus(prompt)
    return (f"# Architektur: {p}\n\n## Komponente: fake-komponente\n"
            f"beschreibung: Trägt alles.\neinheiten: {', '.join(ids)}\nbeziehungen:\n")


def _fake_flows(prompt: str) -> str:
    ids = _ids_aus_uebersicht(prompt)
    p = _projekt_aus(prompt)
    schritte = "\n".join(f"{i+1}. Fake-Schritt {i+1} — {eid}" for i, eid in enumerate(ids[:3]))
    return f"# Flows: {p}\n\n## Flow: fake-ablauf\n{schritte}\n"


def standard_antworten(welt: Welt, lasse_aus: set[str] | None = None) -> None:
    """Registriert gültige Fake-Antworten für alle Scan-Rollen. `lasse_aus`: Symbole,
    die der Erst-Scan absichtlich auslässt (übt den Nachfass-Pfad)."""
    aus = lasse_aus or set()
    welt.antwort_auf(r"-schneiden-", lambda pr: _fake_schneiden(pr, aus))
    welt.antwort_auf(r"-nachfass-", _fake_nachfass)
    welt.antwort_auf(r"-sichten-sicht-features", _fake_features)
    welt.antwort_auf(r"-sichten-sicht-architektur", _fake_architektur)
    welt.antwort_auf(r"-sichten-sicht-flows", _fake_flows)
