"""Security module for sanitizing sensitive data from data structures.

This module ensures that sensitive information like API keys, passwords, and tokens
are not accidentally leaked in logs, files, or API responses.

Includes helpers for filtering research metadata in API responses to prevent
settings_snapshot (which contains all application settings including API keys)
from being sent to the frontend.
"""

import json
from typing import Any, Set


class DataSanitizer:
    """Utility class for removing sensitive information from data structures."""

    # Default set of sensitive key names to redact
    DEFAULT_SENSITIVE_KEYS: Set[str] = {
        "api_key",
        "apikey",
        "password",
        "secret",
        "access_token",
        "refresh_token",
        "private_key",
        "auth_token",
        "session_token",
        "csrf_token",
    }

    @staticmethod
    def sanitize(data: Any, sensitive_keys: Set[str] | None = None) -> Any:
        """
        Recursively remove sensitive keys from data structures.

        This method traverses dictionaries and lists, removing any keys that match
        the sensitive keys list (case-insensitive). This prevents accidental
        credential leakage in optimization results, logs, or API responses.

        Args:
            data: The data structure to sanitize (dict, list, or primitive)
            sensitive_keys: Set of key names to remove (case-insensitive).
                          If None, uses DEFAULT_SENSITIVE_KEYS.

        Returns:
            Sanitized copy of the data with sensitive keys removed

        Example:
            >>> sanitizer = DataSanitizer()
            >>> data = {"username": "user", "api_key": "secret123"}
            >>> sanitizer.sanitize(data)
            {"username": "user"}
        """
        if sensitive_keys is None:
            sensitive_keys = DataSanitizer.DEFAULT_SENSITIVE_KEYS

        # Convert to lowercase for case-insensitive comparison
        sensitive_keys_lower = {key.lower() for key in sensitive_keys}

        if isinstance(data, dict):
            return {
                k: DataSanitizer.sanitize(v, sensitive_keys)
                for k, v in data.items()
                if k.lower() not in sensitive_keys_lower
            }
        if isinstance(data, list):
            return [
                DataSanitizer.sanitize(item, sensitive_keys) for item in data
            ]
        # Return primitives unchanged
        return data

    @staticmethod
    def redact(
        data: Any,
        sensitive_keys: Set[str] | None = None,
        redaction_text: str = "[REDACTED]",
    ) -> Any:
        """
        Recursively redact (replace with placeholder) sensitive values in data structures.

        Unlike sanitize() which removes keys entirely, this method replaces their
        values with a redaction placeholder, preserving the structure.

        Args:
            data: The data structure to redact (dict, list, or primitive)
            sensitive_keys: Set of key names to redact (case-insensitive).
                          If None, uses DEFAULT_SENSITIVE_KEYS.
            redaction_text: Text to replace sensitive values with

        Returns:
            Copy of the data with sensitive values redacted

        Example:
            >>> sanitizer = DataSanitizer()
            >>> data = {"username": "user", "api_key": "secret123"}
            >>> sanitizer.redact(data)
            {"username": "user", "api_key": "[REDACTED]"}
        """
        if sensitive_keys is None:
            sensitive_keys = DataSanitizer.DEFAULT_SENSITIVE_KEYS

        # Convert to lowercase for case-insensitive comparison
        sensitive_keys_lower = {key.lower() for key in sensitive_keys}

        if isinstance(data, dict):
            return {
                k: (
                    redaction_text
                    if k.lower() in sensitive_keys_lower
                    else DataSanitizer.redact(v, sensitive_keys, redaction_text)
                )
                for k, v in data.items()
            }
        if isinstance(data, list):
            return [
                DataSanitizer.redact(item, sensitive_keys, redaction_text)
                for item in data
            ]
        # Return primitives unchanged
        return data


# Convenience functions for direct use
def sanitize_data(data: Any, sensitive_keys: Set[str] | None = None) -> Any:
    """
    Remove sensitive keys from data structures.

    Convenience function that calls DataSanitizer.sanitize().

    Args:
        data: The data structure to sanitize
        sensitive_keys: Optional set of sensitive key names

    Returns:
        Sanitized copy of the data
    """
    return DataSanitizer.sanitize(data, sensitive_keys)


def redact_data(
    data: Any,
    sensitive_keys: Set[str] | None = None,
    redaction_text: str = "[REDACTED]",
) -> Any:
    """
    Redact (replace) sensitive values in data structures.

    Convenience function that calls DataSanitizer.redact().

    Args:
        data: The data structure to redact
        sensitive_keys: Optional set of sensitive key names
        redaction_text: Text to replace sensitive values with

    Returns:
        Copy of the data with sensitive values redacted
    """
    return DataSanitizer.redact(data, sensitive_keys, redaction_text)


def filter_research_metadata(research_meta: Any) -> dict:
    """Filter research_meta to only safe fields for history list API responses.

    Uses an allowlist approach to prevent leaking settings_snapshot
    (which contains API keys, passwords, tokens) to the frontend.
    History list consumers only need is_news_search from metadata.

    Args:
        research_meta: Raw research metadata (dict, JSON string, or None)

    Returns:
        dict with only safe fields extracted (currently: is_news_search)
    """
    try:
        meta = research_meta or {}
        if isinstance(meta, str):
            meta = json.loads(meta)
        if not isinstance(meta, dict):
            return {"is_news_search": False}
        return {
            "is_news_search": bool(meta.get("is_news_search", False)),
        }
    except (json.JSONDecodeError, TypeError, AttributeError):
        return {"is_news_search": False}


def strip_settings_snapshot(research_meta: Any) -> dict:
    """Remove settings_snapshot from research_meta for API responses.

    settings_snapshot contains all application settings including API keys.
    This strips it while preserving all other metadata fields that the
    frontend needs (phase, error_type, processed_query, mode, duration, etc.).

    Args:
        research_meta: Raw research metadata (dict, JSON string, or None)

    Returns:
        Copy of the dict with settings_snapshot removed
    """
    try:
        meta = research_meta or {}
        if isinstance(meta, str):
            meta = json.loads(meta)
        if not isinstance(meta, dict):
            return {}
        return {k: v for k, v in meta.items() if k != "settings_snapshot"}
    except (json.JSONDecodeError, TypeError, AttributeError):
        return {}
