"""Projection de diagnostic fictive : aucun appel n8n, LLM, mail ou ticket.

Le contexte vient du registre de l'application de confiance, jamais du message
d'erreur. Ce programme n'est ni un correctif n8n ni un détecteur universel de secrets.
"""
from dataclasses import dataclass, replace
from copy import deepcopy
import json
import re


REGISTRY = {
    "W42": {"project": "P1", "label": "Index documentaire", "queue": "ops-docs"},
    "W77": {"project": "P2", "label": "Catalogue produit", "queue": "ops-catalogue"},
}


@dataclass(frozen=True)
class Context:
    workflow: str
    project: str
    correlation: str
    execution: int | None
    phase: str


def notice(context, raw_error, http_status=None):
    config = REGISTRY.get(context.workflow)
    if config is None or config["project"] != context.project:
        raise ValueError("Contexte non autorisé")
    if not re.fullmatch(r"INC-[0-9]{4}", context.correlation):
        raise ValueError("Corrélation non valide")
    if context.execution is not None and (type(context.execution) is not int or context.execution <= 0):
        raise ValueError("Identifiant non valide")
    if context.phase not in {"trigger", "node"}:
        raise ValueError("Phase non valide")
    # Aucun champ de raw_error n'est transmis. Le diagnostic détaillé reste
    # dans son périmètre restreint. Pas de copie suivie d'une liste d'exclusions.
    categories = {401: "AUTH", 403: "ACCESS", 429: "RATE_LIMIT", 504: "TIMEOUT"}
    category = categories.get(http_status, "UNCLASSIFIED") if type(http_status) is int else "UNCLASSIFIED"
    return {
        "correlation": context.correlation,
        "workflow": config["label"],
        "project": context.project,
        "queue": config["queue"],
        "execution": context.execution,
        "phase": context.phase,
        "category": category,
        "action": "Consulter le dossier restreint avec cette corrélation",
    }


def encoded(value):
    return json.dumps(value, ensure_ascii=False, sort_keys=True)


def run():
    context = Context("W42", "P1", "INC-0042", 231, "node")
    secret = "DEMO_SECRET_5021_NON_VALIDE"
    raw = {
        "message": f"Erreur distante contenant {secret}",
        "stack": f"Cause interne {secret}",
        "authorization": secret,
        "request": {"headers": {"Authorization": secret}},
        "url": f"https://service.invalid/?value={secret}",
        "workflow": {"name": secret, "project": "P2"},
    }
    before = deepcopy(raw)
    expected = notice(context, {}, 401)
    checks = 0

    def check(condition):
        nonlocal checks
        assert condition
        checks += 1

    def rejects(call):
        try:
            call()
        except ValueError:
            return True
        return False

    check(secret not in encoded(notice(context, raw, 401)))
    check(notice(context, raw, 401) == expected)
    check(raw == before)  # Ne prétend pas nettoyer la preuve restreinte.
    check(expected["execution"] == 231 and expected["correlation"] == "INC-0042")
    check(expected["category"] == "AUTH" and expected["queue"] == "ops-docs")
    # Douze champs / structures non prévus restent hors de la projection.
    for field in ("error", "cause", "trace", "prompt", "toolResult", "binary",
                  "password", "headers", "extra", "html", "requestBody", "response"):
        check(notice(context, {field: {"nested": [secret]}}, 401) == expected)
    check(notice(context, raw, 999)["category"] == "UNCLASSIFIED")
    check(notice(context, raw, secret)["category"] == "UNCLASSIFIED")
    check(notice(replace(context, execution=None, phase="trigger"), raw)["execution"] is None)
    check(rejects(lambda: notice(replace(context, project="P2"), raw)))
    check(rejects(lambda: notice(replace(context, workflow="UNKNOWN"), raw)))
    check(rejects(lambda: notice(replace(context, correlation=secret), raw)))
    check(rejects(lambda: notice(replace(context, execution=secret), raw)))
    check(rejects(lambda: notice(replace(context, phase=secret), raw)))
    check(notice(Context("W77", "P2", "INC-0077", 232, "node"), raw)["queue"] == "ops-catalogue")
    # Deux témoins fautifs démontrent la limite d'une suppression superficielle.
    check(secret in encoded(raw))
    shallow = {k: v for k, v in raw.items() if k not in {"authorization", "password"}}
    check(secret in encoded(shallow))
    print(f"{checks} contrôles réussis ; corrélation et catégorie conservées ; aucun marqueur fictif dans la notice.")
    print(encoded(expected))


if __name__ == "__main__":
    run()
