"""
Multi-turn injection tracker for scandar-guard.

Detects split attacks where injection fragments are spread across
multiple conversation turns. An attacker sends benign-looking messages
that individually don't trigger any pattern, but combined across turns
reconstruct a complete injection.
"""
from __future__ import annotations

from typing import List, Optional

from .findings import RuntimeFinding

# Injection fragment sets: if ALL fragments in a set appear across 2+ messages, flag it
_SPLIT_SEEDS: list[list[str]] = [
    ["ignore", "previous", "instructions"],
    ["disregard", "all", "rules"],
    ["disregard", "guidelines"],
    ["you are now", "admin"],
    ["you are now", "developer mode"],
    ["system prompt", "override"],
    ["forget", "your", "training"],
    ["new instructions", "from now on"],
    ["act as", "unrestricted"],
    ["pretend", "no restrictions"],
    ["ignore", "safety", "guidelines"],
    ["bypass", "content", "filter"],
    ["override", "your", "programming"],
    ["original instructions", "replace"],
    ["between these", "new system"],
]

# Suspicious escalation patterns: if message N references output from message N-k
_ESCALATION_PHRASES = [
    "based on what you just said",
    "now that you've confirmed",
    "since you agreed",
    "as you mentioned",
    "following your instructions",
    "as per your last response",
]


class MultiTurnTracker:
    """
    Tracks message content across conversation turns and detects
    injection patterns that span multiple messages.
    """

    def __init__(self, session_id: str) -> None:
        self.session_id = session_id
        self._message_buffer: list[str] = []  # last 12 messages, lowercased
        self._total_messages = 0

    def record_message(self, content: str, role: str = "user") -> List[RuntimeFinding]:
        """Record a message and check for cross-turn injection patterns."""
        self._total_messages += 1
        lower = content.lower()[:2000]  # Cap to prevent memory issues
        self._message_buffer.append(lower)
        if len(self._message_buffer) > 12:
            self._message_buffer = self._message_buffer[-12:]

        findings: List[RuntimeFinding] = []

        # Only check user messages (attackers inject via user role)
        if role.lower() in ("user", "tool"):
            findings.extend(self._check_split_injection())
            findings.extend(self._check_escalation(lower))

        return findings

    def _check_split_injection(self) -> List[RuntimeFinding]:
        """Check if injection fragments from different messages combine into a pattern."""
        if len(self._message_buffer) < 2:
            return []

        findings: List[RuntimeFinding] = []
        combined = " ".join(self._message_buffer)

        for seed in _SPLIT_SEEDS:
            if all(fragment in combined for fragment in seed):
                # Verify fragments are spread across 2+ different messages
                msgs_with_hits = 0
                for msg in self._message_buffer:
                    if any(fragment in msg for fragment in seed):
                        msgs_with_hits += 1

                if msgs_with_hits >= 2:
                    findings.append(
                        RuntimeFinding(
                            session_id=self.session_id,
                            severity="high",
                            category="SPLIT_INJECTION",
                            title="Multi-turn injection attempt detected",
                            description=(
                                f"Injection fragments found across {msgs_with_hits} messages: "
                                f"{' + '.join(repr(f) for f in seed)}. "
                                "This pattern is consistent with a split injection attack "
                                "where the payload is distributed across conversation turns "
                                "to evade single-message pattern matching."
                            ),
                            source="session",
                            confidence=0.78,
                            context={
                                "fragments": seed,
                                "messages_involved": msgs_with_hits,
                                "total_messages_in_window": len(self._message_buffer),
                            },
                        )
                    )

        return findings

    def _check_escalation(self, current_message: str) -> List[RuntimeFinding]:
        """
        Detect social engineering escalation: the attacker references
        the model's own output to build false authority.
        """
        findings: List[RuntimeFinding] = []
        for phrase in _ESCALATION_PHRASES:
            if phrase in current_message:
                # Check if there's also an injection-like intent in the same message
                injection_hints = [
                    "send", "transfer", "email", "post to", "upload",
                    "execute", "run", "delete", "modify", "change",
                    "admin", "override", "unrestricted",
                ]
                if any(hint in current_message for hint in injection_hints):
                    findings.append(
                        RuntimeFinding(
                            session_id=self.session_id,
                            severity="medium",
                            category="SOCIAL_ENGINEERING_ESCALATION",
                            title="Possible social engineering escalation",
                            description=(
                                f"Message references the model's prior output ('{phrase}') "
                                "alongside an action request. This pattern is consistent with "
                                "social engineering attacks that build false authority across turns."
                            ),
                            source="session",
                            confidence=0.65,
                            context={"escalation_phrase": phrase},
                        )
                    )
                    break  # One finding per message

        return findings
