"""
Tool call and tool result inspector for scandar-guard.

inspect_tool_result() is the most critical capability:
it catches prompt injection embedded in external content
that tool calls return to the model (files, web pages, API responses).
"""
from __future__ import annotations

import json
import time
from typing import Any

from .findings import InspectionResult, RuntimeFinding
from .inspector import MessageInspector
from .patterns import (
    EXFILTRATION_PATTERNS,
    PII_PATTERNS,
    PROMPT_INJECTION_PATTERNS,
    SHELL_INJECTION_PATTERNS,
    PatternDef,
)

_inspector = MessageInspector()

# Tool names that are always suspicious
_RISKY_TOOL_NAMES = {"eval", "exec", "__import__", "subprocess", "os.system", "shell"}


class ToolCallInspector:
    def inspect_tool_call(
        self,
        tool_name: str,
        arguments: dict[str, Any],
        session_id: str,
    ) -> InspectionResult:
        """Inspect tool call arguments for PII, shell injection, and suspicious content."""
        start = time.perf_counter()
        findings: list[RuntimeFinding] = []

        # Flag risky tool names
        if tool_name.lower() in _RISKY_TOOL_NAMES:
            findings.append(
                RuntimeFinding(
                    session_id=session_id,
                    severity="critical",
                    category="RISKY_TOOL_NAME",
                    title=f"High-risk tool called: {tool_name}",
                    description=(
                        f"The model called '{tool_name}', which is inherently high-risk "
                        "and rarely appropriate in production agent workflows."
                    ),
                    source="tool_call",
                    confidence=0.95,
                    context={"tool_name": tool_name},
                )
            )

        # Flatten and inspect arguments
        serialized = self._serialize_args(arguments)
        for field_name, value_str in serialized.items():
            for patterns, source in [
                (PII_PATTERNS, "tool_call"),
                (SHELL_INJECTION_PATTERNS, "tool_call"),
                (EXFILTRATION_PATTERNS, "tool_call"),
            ]:
                for p in patterns:
                    match = p.pattern.search(value_str)
                    if match:
                        findings.append(
                            RuntimeFinding(
                                session_id=session_id,
                                severity=p.severity,
                                category=p.category,
                                title=p.title,
                                description=f"Detected in tool argument '{field_name}': {p.title.lower()}",
                                source="tool_call",
                                confidence=p.confidence,
                                context={
                                    "tool_name": tool_name,
                                    "arg_name": field_name,
                                    "matched_excerpt": _inspector._truncate_context(
                                        value_str, match.start(), match.end()
                                    ),
                                },
                            )
                        )

        return InspectionResult(
            findings=findings,
            session_id=session_id,
            event_type=f"tool_call_inspected:{tool_name}",
            duration_ms=(time.perf_counter() - start) * 1000,
        )

    def inspect_tool_result(
        self,
        tool_name: str,
        result: Any,
        session_id: str,
    ) -> InspectionResult:
        """
        Inspect tool results for prompt injection.

        This is the most critical runtime security capability: external content
        (web pages, files, API responses) returned by tool calls can contain
        injected instructions. This method catches them before the model sees them.
        """
        start = time.perf_counter()
        findings: list[RuntimeFinding] = []

        result_str = self._result_to_string(result)
        # Cap at 10k chars to prevent ReDoS on huge responses
        inspect_text = result_str[:10_000]

        for patterns in [
            PROMPT_INJECTION_PATTERNS,
            SHELL_INJECTION_PATTERNS,
        ]:
            for p in patterns:
                match = p.pattern.search(inspect_text)
                if match:
                    findings.append(
                        RuntimeFinding(
                            session_id=session_id,
                            severity=p.severity,
                            category=p.category,
                            title=f"[Tool Result] {p.title}",
                            description=(
                                f"Detected in result from tool '{tool_name}': {p.title.lower()}. "
                                "External content returned by this tool contains a potentially "
                                "malicious instruction that could hijack agent behavior."
                            ),
                            source="tool_result",
                            confidence=p.confidence,
                            context={
                                "tool_name": tool_name,
                                "matched_excerpt": _inspector._truncate_context(
                                    inspect_text, match.start(), match.end()
                                ),
                            },
                        )
                    )

        return InspectionResult(
            findings=findings,
            session_id=session_id,
            event_type=f"tool_result_inspected:{tool_name}",
            duration_ms=(time.perf_counter() - start) * 1000,
        )

    def _serialize_args(self, arguments: dict[str, Any]) -> dict[str, str]:
        """Flatten argument values to strings, truncated for safety."""
        result: dict[str, str] = {}
        for key, value in arguments.items():
            if isinstance(value, str):
                result[key] = value[:500]
            elif isinstance(value, (int, float, bool)):
                result[key] = str(value)
            elif value is None:
                result[key] = ""
            else:
                try:
                    result[key] = json.dumps(value)[:500]
                except (TypeError, ValueError):
                    result[key] = str(value)[:500]
        return result

    def _result_to_string(self, result: Any) -> str:
        """Convert any tool result to a string for inspection."""
        if isinstance(result, str):
            return result
        if result is None:
            return ""
        if isinstance(result, (int, float, bool)):
            return str(result)
        try:
            return json.dumps(result)
        except (TypeError, ValueError):
            return str(result)
