"""
Practice: scoring injection detectors (solution). Lab 15's script 02 (02_injection_classifier.py) and
classifier.py.
"""
import re

# The lab's keyword rules (classifier.py), unchanged.
KEYWORD_PATTERNS = [
    r"\b(ignore|disregard|forget|skip|override)\b.{0,40}\b(previous|prior|above|earlier|all|your|the)\b.{0,30}"
    r"\b(instructions?|prompts?|rules?|directions?|tasks?|context|text)\b",
    r"\b(reveal|show|print|repeat|output|tell me|give me|spell|reverse)\b.{0,40}"
    r"\b(system prompt|your prompt|instructions?|password|secret)\b",
    r"\b(system prompt|initial prompt|hidden prompt)\b",
    r"\byou are now\b|\bnew (task|instructions?|role)\b|\bfrom now on\b",
    r"\b(vergiss|ignoriere)\b",  # German: the deepset data has it
]


def keyword_score(text):
    """1.0 if any of KEYWORD_PATTERNS matches the text (ignoring case, '.' matching newlines),
    else 0.0. Keyword rules give yes or no, never a score in between."""
    return 1.0 if any(re.search(p, text, re.IGNORECASE | re.DOTALL) for p in KEYWORD_PATTERNS) else 0.0


def flagged(scores, threshold=0.5):
    """Indexes of the messages a detector flags: score at or above the threshold."""
    return [i for i, s in enumerate(scores) if s >= threshold]


def confusion(scores, labels, threshold=0.5):
    """Score a detector on a labeled set (label 1 = attack, 0 = normal). Return a tuple
    (caught, attacks, blocked, normal): attacks flagged, attacks in the set, normal messages
    flagged, normal messages in the set."""
    caught = sum(1 for s, y in zip(scores, labels) if y == 1 and s >= threshold)
    blocked = sum(1 for s, y in zip(scores, labels) if y == 0 and s >= threshold)
    attacks = sum(1 for y in labels if y == 1)
    return (caught, attacks, blocked, len(labels) - attacks)


def sweep(scores, labels, thresholds):
    """For each threshold, in order: (threshold, attacks caught, normal messages blocked)."""
    out = []
    for th in thresholds:
        caught, _, blocked, _ = confusion(scores, labels, th)
        out.append((th, caught, blocked))
    return out


def blocked_by_all(rows, threshold=0.5):
    """`rows` are (question, score_1, score_2, ...), one score per detector. Return the questions
    every detector flags, in order."""
    return [q for q, *scores in rows if all(s >= threshold for s in scores)]


def route(score, threshold=0.5, block=False):
    """What happens to a message. Not flagged: "answer". Flagged and the detector blocks:
    "block" (the learner gets no answer). Flagged and the detector is only a signal: "review"
    (it's answered, and logged for a person to look at)."""
    if score < threshold:
        return "answer"
    return "block" if block else "review"
