"""
Per-agent behavioral profiles for scandar-guard.

Learns normal tool call patterns over multiple sessions and flags
deviations. After N sessions of baseline data, Guard can distinguish
"this agent always uses search + summarize" from "this agent suddenly
called send_email for the first time" — context-aware anomaly detection.

Profiles persist as JSON files (one per agent_id) and are loaded/saved
automatically. No external database required.
"""
from __future__ import annotations

import json
import os
from typing import Optional

from .findings import RuntimeFinding


class AgentProfile:
    """
    Per-agent behavioral profile that persists between sessions.
    Learns the agent's normal tool usage pattern and flags deviations.
    """

    MIN_SESSIONS_FOR_BASELINE = 5

    def __init__(
        self,
        agent_id: str,
        profile_dir: str = "./scandar-profiles",
    ) -> None:
        self.agent_id = agent_id
        self.profile_dir = profile_dir
        self._data = self._load()

    def _profile_path(self) -> str:
        safe_id = "".join(c if c.isalnum() or c in "-_" else "_" for c in self.agent_id)
        return os.path.join(self.profile_dir, f"{safe_id}.json")

    def _load(self) -> dict:
        try:
            with open(self._profile_path(), "r", encoding="utf-8") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError, OSError):
            return {
                "agent_id": self.agent_id,
                "session_count": 0,
                "tool_frequency": {},
                "known_tool_set": [],
                "avg_tools_per_session": 0.0,
                "avg_findings_per_session": 0.0,
                "typical_session_length": 0.0,
            }

    def save(self) -> None:
        try:
            os.makedirs(self.profile_dir, exist_ok=True)
            with open(self._profile_path(), "w", encoding="utf-8") as f:
                json.dump(self._data, f, indent=2)
        except OSError:
            pass  # Never crash the agent

    def record_session(
        self,
        tool_calls: list[str],
        message_count: int,
        findings_count: int,
    ) -> None:
        """Update the profile with data from a completed session."""
        d = self._data
        n = d["session_count"]

        for tool in tool_calls:
            d["tool_frequency"][tool] = d["tool_frequency"].get(tool, 0) + 1

        d["known_tool_set"] = list(set(d["known_tool_set"] + tool_calls))
        d["avg_tools_per_session"] = (d["avg_tools_per_session"] * n + len(tool_calls)) / (n + 1)
        d["avg_findings_per_session"] = (d["avg_findings_per_session"] * n + findings_count) / (n + 1)
        d["typical_session_length"] = (d["typical_session_length"] * n + message_count) / (n + 1)
        d["session_count"] = n + 1

        self.save()

    def check_tool_deviation(
        self, tool_name: str, session_id: str
    ) -> Optional[RuntimeFinding]:
        """Check if this tool call deviates from the learned profile."""
        d = self._data
        if d["session_count"] < self.MIN_SESSIONS_FOR_BASELINE:
            return None

        if tool_name not in d["known_tool_set"]:
            return RuntimeFinding(
                session_id=session_id,
                severity="medium",
                category="PROFILE_DEVIATION",
                title=f"Tool not in agent profile: {tool_name}",
                description=(
                    f"Agent '{self.agent_id}' has never used '{tool_name}' across "
                    f"{d['session_count']} previous sessions. This tool is not part of "
                    "the learned behavioral baseline. This may indicate agent compromise, "
                    "prompt injection causing tool misuse, or a misconfiguration."
                ),
                source="session",
                confidence=0.72,
                context={
                    "tool_name": tool_name,
                    "known_tools": d["known_tool_set"],
                    "sessions_analyzed": d["session_count"],
                },
            )
        return None

    def check_frequency_deviation(
        self, tool_call_count: int, session_id: str
    ) -> Optional[RuntimeFinding]:
        """Check if the session tool call count deviates from the profile baseline."""
        d = self._data
        if d["session_count"] < self.MIN_SESSIONS_FOR_BASELINE:
            return None

        avg = d["avg_tools_per_session"]
        if avg > 0 and tool_call_count > avg * 3:
            return RuntimeFinding(
                session_id=session_id,
                severity="medium",
                category="PROFILE_FREQUENCY_DEVIATION",
                title="Unusual tool call volume for this agent",
                description=(
                    f"This session has made {tool_call_count} tool calls, "
                    f"which is {tool_call_count / avg:.1f}x the historical average "
                    f"of {avg:.1f} per session. This may indicate the agent is caught "
                    "in an unexpected loop or is being actively manipulated."
                ),
                source="session",
                confidence=0.68,
                context={
                    "session_tool_calls": tool_call_count,
                    "profile_avg": round(avg, 1),
                    "sessions_analyzed": d["session_count"],
                },
            )
        return None

    @property
    def session_count(self) -> int:
        return self._data["session_count"]

    @property
    def known_tools(self) -> list[str]:
        return self._data["known_tool_set"]

    @property
    def has_baseline(self) -> bool:
        return self._data["session_count"] >= self.MIN_SESSIONS_FOR_BASELINE
