"""Security Gateway — THE ONLY file that imports `anthropic`.

Scans all outbound payloads to ensure no data values, error messages,
PII, or sensitive information is sent to the Claude API.
"""

from __future__ import annotations

import hashlib
import re
from datetime import datetime, timezone
from typing import Any

import anthropic

from config import AppSettings
from security.audit_log import AuditLog


class DataLeakageError(Exception):
    """Raised when an outbound payload contains potentially sensitive data."""
    pass


class SecurityGateway:
    """The ONLY gateway through which Claude API calls are made.

    Responsibilities:
    1. Scan every outbound message for data leakage patterns
    2. Block payloads that contain actual data values
    3. Log every API interaction to the audit table
    4. Return Claude responses
    """

    # Patterns that indicate data values (NOT metadata)
    _DATA_PATTERNS = [
        # Email addresses
        re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b", re.IGNORECASE),
        # Phone numbers
        re.compile(r"\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b"),
        # SSN
        re.compile(r"\b\d{3}-\d{2}-\d{4}\b"),
        # IP addresses
        re.compile(r"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"),
        # Dollar/Euro amounts with values
        re.compile(r"[\$€£]\s*[\d,]+\.?\d*"),
        # CSV-like data (3+ comma-separated values that look like data rows)
        re.compile(r"(?:\d+[,;]\s*){3,}"),
        # Connection strings
        re.compile(r"(?:Server|Host|Data Source)\s*=\s*\S+", re.IGNORECASE),
        # Password patterns
        re.compile(r"(?:password|pwd|pass)\s*[=:]\s*\S+", re.IGNORECASE),
    ]

    # Known safe patterns to whitelist (these look like data but are metadata)
    _SAFE_PATTERNS = [
        re.compile(r"precision=\d+"),
        re.compile(r"scale=\d+"),
        re.compile(r"max_length=\d+"),
        re.compile(r"Approximate rows: [\d,]+"),
        re.compile(r"pool_size=\d+"),
    ]

    def __init__(self, settings: AppSettings, audit_log: AuditLog) -> None:
        self._client = anthropic.Anthropic(api_key=settings.claude.api_key)
        self._model = settings.claude.model
        self._max_tokens = settings.claude.max_tokens
        self._temperature = settings.claude.temperature
        self._audit = audit_log
        self._extra_blocked: list[re.Pattern] = []

        # Load additional blocked patterns from config
        for pattern_str in settings.security.blocked_patterns:
            try:
                self._extra_blocked.append(re.compile(pattern_str, re.IGNORECASE))
            except re.error:
                pass

    def _is_safe_match(self, match_text: str) -> bool:
        """Check if a flagged match is actually a known-safe metadata pattern."""
        for safe_pat in self._SAFE_PATTERNS:
            if safe_pat.search(match_text):
                return True
        return False

    def scan_payload(self, text: str) -> list[str]:
        """Scan text for potential data leakage. Returns list of violations."""
        violations = []

        for pattern in self._DATA_PATTERNS + self._extra_blocked:
            matches = pattern.finditer(text)
            for match in matches:
                match_text = match.group()
                # Check context — is this a safe metadata pattern?
                start = max(0, match.start() - 30)
                end = min(len(text), match.end() + 30)
                context = text[start:end]
                if not self._is_safe_match(context):
                    violations.append(f"Pattern match: '{match_text[:50]}...' ({pattern.pattern[:40]})")

        return violations

    def _hash_payload(self, text: str) -> str:
        return hashlib.sha256(text.encode()).hexdigest()[:16]

    async def send_to_claude(
        self,
        system_prompt: str,
        messages: list[dict[str, str]],
        metadata: dict[str, Any] | None = None,
    ) -> str:
        """Send a message to Claude API with full security scanning.

        Args:
            system_prompt: The system message (schema context)
            messages: List of {"role": "user"/"assistant", "content": "..."} messages
            metadata: Optional metadata for audit logging

        Returns:
            Claude's response text

        Raises:
            DataLeakageError: If outbound payload contains sensitive data
        """
        # Scan system prompt
        violations = self.scan_payload(system_prompt)
        if violations:
            await self._audit.log(
                event_type="BLOCKED_OUTBOUND",
                direction="outbound",
                payload_hash=self._hash_payload(system_prompt),
                payload_size=len(system_prompt),
                contains_data=True,
                metadata={"violations": violations, **(metadata or {})},
            )
            raise DataLeakageError(
                f"System prompt contains potential data values: {violations}"
            )

        # Scan each message
        for msg in messages:
            msg_violations = self.scan_payload(msg["content"])
            # User questions are allowed to contain values — they come from the user
            if msg["role"] != "user" and msg_violations:
                await self._audit.log(
                    event_type="BLOCKED_OUTBOUND",
                    direction="outbound",
                    payload_hash=self._hash_payload(msg["content"]),
                    payload_size=len(msg["content"]),
                    contains_data=True,
                    metadata={"violations": msg_violations, "role": msg["role"], **(metadata or {})},
                )
                raise DataLeakageError(
                    f"Message contains potential data values: {msg_violations}"
                )

        # Build full payload text for size logging
        full_payload = system_prompt + " ".join(m["content"] for m in messages)

        # Log outbound
        await self._audit.log(
            event_type="API_CALL",
            direction="outbound",
            payload_hash=self._hash_payload(full_payload),
            payload_size=len(full_payload),
            contains_data=False,
            metadata={"model": self._model, "message_count": len(messages), **(metadata or {})},
        )

        # Make the API call
        response = self._client.messages.create(
            model=self._model,
            max_tokens=self._max_tokens,
            temperature=self._temperature,
            system=system_prompt,
            messages=messages,
        )

        response_text = response.content[0].text if response.content else ""

        # Log inbound
        await self._audit.log(
            event_type="API_RESPONSE",
            direction="inbound",
            payload_hash=self._hash_payload(response_text),
            payload_size=len(response_text),
            contains_data=False,
            metadata={
                "model": self._model,
                "input_tokens": response.usage.input_tokens,
                "output_tokens": response.usage.output_tokens,
                **(metadata or {}),
            },
        )

        return response_text
