"""
Behavioral anomaly tracker for scandar-guard.

Middle ground between pure pattern matching and ML-based detection.
No training data required — works from session one.
Tracks tool call patterns and flags suspicious sequences and deviations.
"""
from __future__ import annotations

import time
from typing import Optional

from .findings import RuntimeFinding
from .patterns import SUSPICIOUS_SEQUENCES


class AnomalyTracker:
    def __init__(self, session_id: str) -> None:
        self.session_id = session_id
        self._tool_history: list[str] = []        # last 15 tool calls
        self._call_timestamps: list[float] = []   # unix timestamps for volume tracking
        self._seen_tools: set[str] = set()        # unique tools seen this session
        self._total_calls = 0

    def record_tool_call(self, tool_name: str) -> list[RuntimeFinding]:
        """Record a tool call and return any anomaly findings (may be empty)."""
        findings: list[RuntimeFinding] = []
        self._total_calls += 1
        now = time.monotonic()
        self._call_timestamps.append(now)
        # Keep rolling window
        self._call_timestamps = [t for t in self._call_timestamps if now - t <= 60]
        # Maintain last 15 tool calls
        self._tool_history.append(tool_name)
        if len(self._tool_history) > 15:
            self._tool_history = self._tool_history[-15:]

        new_tool_finding = self._check_new_tool(tool_name)
        if new_tool_finding:
            findings.append(new_tool_finding)

        seq_finding = self._check_suspicious_sequence(tool_name)
        if seq_finding:
            findings.append(seq_finding)

        vol_finding = self._check_volume_spike()
        if vol_finding:
            findings.append(vol_finding)

        self._seen_tools.add(tool_name)
        return findings

    def _check_new_tool(self, tool_name: str) -> Optional[RuntimeFinding]:
        """Flag a new tool introduced after the agent has established a pattern."""
        if len(self._seen_tools) >= 3 and tool_name not in self._seen_tools:
            return RuntimeFinding(
                session_id=self.session_id,
                severity="info",
                category="NEW_TOOL_MID_SESSION",
                title=f"New tool introduced mid-session: {tool_name}",
                description=(
                    f"The agent called '{tool_name}' for the first time after already "
                    f"using {len(self._seen_tools)} other tools this session. "
                    "Legitimate agents typically don't introduce new tool types mid-conversation."
                ),
                source="session",
                confidence=0.6,
                context={"tool_name": tool_name, "prior_tool_count": len(self._seen_tools)},
            )
        return None

    def _check_suspicious_sequence(self, tool_name: str) -> Optional[RuntimeFinding]:
        """Check the recent tool call history against known bad sequences."""
        recent = self._tool_history[-3:] if len(self._tool_history) >= 3 else self._tool_history
        for seq_def in SUSPICIOUS_SEQUENCES:
            seq = seq_def["sequence"]
            # Check if the tail of recent calls matches this sequence
            if len(recent) >= len(seq):
                window = recent[-len(seq):]
                # Normalize tool names for matching (lowercase, strip underscores)
                def norm(s: str) -> str:
                    return s.lower().replace("-", "_")
                if [norm(t) for t in window] == [norm(s) for s in seq]:
                    return RuntimeFinding(
                        session_id=self.session_id,
                        severity=seq_def["severity"],  # type: ignore[arg-type]
                        category=seq_def["category"],
                        title=seq_def["title"],
                        description=(
                            f"Detected suspicious tool call sequence: "
                            f"{' → '.join(seq)}. This pattern is commonly associated "
                            "with data exfiltration or privilege escalation attacks."
                        ),
                        source="session",
                        confidence=0.8,
                        context={"sequence": seq, "recent_calls": list(recent)},
                    )
        return None

    def _check_volume_spike(self) -> Optional[RuntimeFinding]:
        """Flag unusual tool call frequency spikes."""
        if self._total_calls < 10:
            return None  # Not enough data for a meaningful baseline
        recent_30s = sum(1 for t in self._call_timestamps if time.monotonic() - t <= 30)
        # Session average calls per 30 seconds
        session_age_s = max(1.0, (self._call_timestamps[-1] - self._call_timestamps[0]) if len(self._call_timestamps) > 1 else 30.0)
        avg_per_30s = (self._total_calls / session_age_s) * 30
        if avg_per_30s > 0 and recent_30s > max(3 * avg_per_30s, 10):
            return RuntimeFinding(
                session_id=self.session_id,
                severity="medium",
                category="TOOL_CALL_VOLUME_SPIKE",
                title="Unusual tool call frequency spike",
                description=(
                    f"Detected {recent_30s} tool calls in the last 30 seconds "
                    f"(session average: {avg_per_30s:.1f}/30s). "
                    "This may indicate an agent caught in an unexpected loop or active attack."
                ),
                source="session",
                confidence=0.7,
                context={"recent_30s": recent_30s, "session_avg_per_30s": round(avg_per_30s, 1)},
            )
        return None
