"""
guardrails.py — the runtime safety stack (Knowledge Module 08 §7-8).

Defense in depth: input guardrails (PII redaction + jailbreak/injection
detection) and output guardrails (PII/secret leak filter + schema/citation
check). These regex/pattern detectors are deterministic stand-ins for:
  - Azure AI Content Safety  (harm categories + Prompt Shields)
  - Microsoft Presidio / Azure AI Language PII detection
The ARCHITECTURE (in + out, fail-closed) is the point — and is what you keep
when you swap in the cloud services.
"""
from __future__ import annotations

import re

# --- PII detection / redaction ---------------------------------------------
# Banking PII entities. Order matters (redact longer/structured patterns first).
PII_PATTERNS = [
    ("IBAN", re.compile(r"\bAE\d{2}[0-9]{19}\b|\bAE\d{2}(?:\s?\d{4}){5}\b")),
    ("CARD_PAN", re.compile(r"\b(?:\d[ -]?){13,19}\b")),
    ("EMIRATES_ID", re.compile(r"\b784-?\d{4}-?\d{7}-?\d\b")),
    ("EMAIL", re.compile(r"\b[\w.+-]+@[\w-]+\.[\w.-]+\b")),
    ("PHONE", re.compile(r"\b(?:\+971|0)\s?5\d(?:[ -]?\d){7}\b")),
]


def redact_pii(text: str) -> tuple[str, list[str]]:
    """Replace PII with typed placeholders. Returns (redacted_text, found_types).
    Run this BEFORE sending to the model and BEFORE logging (K09 §6)."""
    found: list[str] = []
    out = text
    for label, pat in PII_PATTERNS:
        if pat.search(out):
            found.append(label)
            out = pat.sub(f"[{label}]", out)
    return out, found


# --- jailbreak / prompt-injection detection (Prompt Shields stand-in) -------
_INJECTION_SIGNS = [
    r"ignore (all|any|previous|prior) (instructions|rules)",
    r"disregard (the|your) (system|previous)",
    r"you are now\b",
    r"developer mode",
    r"reveal (your|the) (system prompt|instructions)",
    r"pretend (you are|to be)",
    r"\bsystem\s*:\s*",                 # fake system turn smuggled into content
    r"override code",
]
_INJECTION_RE = re.compile("|".join(_INJECTION_SIGNS), re.IGNORECASE)


def detect_injection(text: str) -> bool:
    """Flag direct jailbreak / indirect-injection attempts. In production this is
    Azure Content Safety Prompt Shields. NOTE (K03 §13, K06 §7): detection is a
    layer, not the cure — the real safety is least-privilege tools + human
    approval, so even a missed injection can't move money."""
    return bool(_INJECTION_RE.search(text))


# --- output guardrails ------------------------------------------------------
_SECRET_RE = re.compile(r"OVR-\d+|sk-[A-Za-z0-9]{8,}|password\s*[:=]", re.IGNORECASE)


def output_is_safe(answer: str, require_citation: bool = False) -> dict:
    """Check an outgoing answer: no leaked secrets, no residual PII, and (for
    grounded answers) a citation present. Fail-closed: if unsafe, the caller
    must block/replace the answer."""
    _, pii = redact_pii(answer)
    leaked_secret = bool(_SECRET_RE.search(answer))
    has_citation = bool(re.search(r"\[[\w.-]+(?::\d+)?\]", answer))
    ok = (not leaked_secret) and (not pii) and (has_citation or not require_citation)
    return {"ok": ok, "leaked_secret": leaked_secret, "residual_pii": pii,
            "has_citation": has_citation}
