"""
Advanced Sanitization Utilities
Provides specialized sanitizers for different content types
"""
import re
import html
import json
from typing import Any, Dict, List, Optional, Union
from datetime import datetime
import logging

logger = logging.getLogger(__name__)


class ContentSanitizer:
    """
    Content-specific sanitization methods
    """

    # Allowed HTML tags for rich text (if needed)
    ALLOWED_TAGS = ['p', 'br', 'b', 'i', 'u', 'strong', 'em', 'ul', 'ol', 'li', 'a', 'span']
    ALLOWED_ATTRIBUTES = {'a': ['href', 'title'], 'span': ['class']}

    @classmethod
    def sanitize_rich_text(cls, content: str, max_length: int = 10000) -> str:
        """
        Sanitize rich text content while preserving safe HTML
        """
        if not content:
            return ""

        # For now, escape all HTML - implement allowlist if needed
        content = html.escape(content)

        # Truncate
        if len(content) > max_length:
            content = content[:max_length]

        return content

    @classmethod
    def sanitize_markdown(cls, content: str, max_length: int = 10000) -> str:
        """
        Sanitize markdown content
        """
        if not content:
            return ""

        # Remove potential HTML in markdown
        content = re.sub(r'<[^>]+>', '', content)

        # Remove javascript: links
        content = re.sub(r'\[([^\]]+)\]\(javascript:[^)]+\)', r'\1', content, flags=re.IGNORECASE)

        # Truncate
        if len(content) > max_length:
            content = content[:max_length]

        return content

    @classmethod
    def sanitize_json_string(cls, data: Union[str, dict]) -> dict:
        """
        Sanitize JSON data recursively
        """
        if isinstance(data, str):
            try:
                data = json.loads(data)
            except json.JSONDecodeError:
                return {}

        if not isinstance(data, dict):
            return {}

        return cls._sanitize_dict(data)

    @classmethod
    def _sanitize_dict(cls, data: dict, depth: int = 0, max_depth: int = 10) -> dict:
        """
        Recursively sanitize dictionary values
        """
        if depth > max_depth:
            return {}

        sanitized = {}
        for key, value in data.items():
            # Sanitize key
            key = cls._sanitize_key(key)
            if not key:
                continue

            # Sanitize value based on type
            if isinstance(value, str):
                sanitized[key] = cls._sanitize_value(value)
            elif isinstance(value, dict):
                sanitized[key] = cls._sanitize_dict(value, depth + 1, max_depth)
            elif isinstance(value, list):
                sanitized[key] = cls._sanitize_list(value, depth + 1, max_depth)
            elif isinstance(value, (int, float, bool)) or value is None:
                sanitized[key] = value
            else:
                # Convert other types to string and sanitize
                sanitized[key] = cls._sanitize_value(str(value))

        return sanitized

    @classmethod
    def _sanitize_list(cls, data: list, depth: int = 0, max_depth: int = 10) -> list:
        """
        Recursively sanitize list values
        """
        if depth > max_depth:
            return []

        sanitized = []
        for item in data:
            if isinstance(item, str):
                sanitized.append(cls._sanitize_value(item))
            elif isinstance(item, dict):
                sanitized.append(cls._sanitize_dict(item, depth + 1, max_depth))
            elif isinstance(item, list):
                sanitized.append(cls._sanitize_list(item, depth + 1, max_depth))
            elif isinstance(item, (int, float, bool)) or item is None:
                sanitized.append(item)
            else:
                sanitized.append(cls._sanitize_value(str(item)))

        return sanitized

    @classmethod
    def _sanitize_key(cls, key: str) -> str:
        """Sanitize dictionary key"""
        if not isinstance(key, str):
            key = str(key)

        # Remove dangerous characters
        key = re.sub(r'[<>{}()\[\]\\;\'"]', '', key)

        # Limit length
        return key[:100]

    @classmethod
    def _sanitize_value(cls, value: str, max_length: int = 10000) -> str:
        """Sanitize string value"""
        if not value:
            return ""

        # Remove null bytes
        value = value.replace('\x00', '')

        # Escape HTML
        value = html.escape(value)

        # Truncate
        if len(value) > max_length:
            value = value[:max_length]

        return value


class ChatMessageSanitizer:
    """
    Specialized sanitizer for chat messages
    """

    # Max message length
    MAX_MESSAGE_LENGTH = 4000

    # Patterns that might indicate prompt injection attempts
    PROMPT_INJECTION_PATTERNS = [
        r"ignore (all )?(previous|above|prior) instructions",
        r"disregard (all )?(previous|above|prior)",
        r"forget (everything|all|what)",
        r"you are now",
        r"new instructions:",
        r"system prompt:",
        r"</?(system|user|assistant)>",
        r"\[INST\]",
        r"\[/INST\]",
        r"<<SYS>>",
        r"<</SYS>>",
    ]

    @classmethod
    def sanitize_message(cls, message: str) -> str:
        """
        Sanitize user chat message
        """
        if not message:
            return ""

        # Basic sanitization
        message = message.strip()

        # Remove null bytes
        message = message.replace('\x00', '')

        # Normalize whitespace (but preserve paragraphs)
        message = re.sub(r'[ \t]+', ' ', message)
        message = re.sub(r'\n{3,}', '\n\n', message)

        # Truncate
        if len(message) > cls.MAX_MESSAGE_LENGTH:
            message = message[:cls.MAX_MESSAGE_LENGTH]

        return message

    @classmethod
    def detect_prompt_injection(cls, message: str) -> bool:
        """
        Detect potential prompt injection attempts
        """
        if not message:
            return False

        message_lower = message.lower()

        for pattern in cls.PROMPT_INJECTION_PATTERNS:
            if re.search(pattern, message_lower, re.IGNORECASE):
                logger.warning(f"Potential prompt injection detected: {message[:100]}...")
                return True

        return False

    @classmethod
    def sanitize_system_prompt(cls, prompt: str, max_length: int = 2000) -> str:
        """
        Sanitize system prompt (admin-provided)
        """
        if not prompt:
            return ""

        # Remove null bytes
        prompt = prompt.replace('\x00', '')

        # Normalize whitespace
        prompt = ' '.join(prompt.split())

        # Truncate
        if len(prompt) > max_length:
            prompt = prompt[:max_length]

        return prompt


class WebhookPayloadSanitizer:
    """
    Sanitizer for webhook payloads
    """

    MAX_PAYLOAD_SIZE = 1024 * 1024  # 1MB

    @classmethod
    def sanitize_payload(cls, payload: dict) -> dict:
        """
        Sanitize webhook payload data
        """
        # Convert to JSON and back to ensure valid structure
        try:
            json_str = json.dumps(payload)

            # Check size
            if len(json_str) > cls.MAX_PAYLOAD_SIZE:
                raise ValueError("Payload too large")

            # Parse back
            payload = json.loads(json_str)

        except (json.JSONDecodeError, TypeError) as e:
            logger.error(f"Invalid webhook payload: {e}")
            return {}

        # Sanitize recursively
        return ContentSanitizer.sanitize_json_string(payload)


class APIKeySanitizer:
    """
    Sanitizer for API keys and tokens
    """

    @classmethod
    def mask_api_key(cls, api_key: str, visible_chars: int = 8) -> str:
        """
        Mask API key for logging/display
        """
        if not api_key:
            return ""

        if len(api_key) <= visible_chars:
            return "*" * len(api_key)

        return api_key[:visible_chars] + "*" * (len(api_key) - visible_chars)

    @classmethod
    def validate_api_key_format(cls, api_key: str) -> bool:
        """
        Validate API key format
        """
        if not api_key:
            return False

        # Check length
        if len(api_key) < 20 or len(api_key) > 64:
            return False

        # Check characters (alphanumeric, underscore, hyphen)
        if not re.match(r'^[a-zA-Z0-9_-]+$', api_key):
            return False

        return True


class LogSanitizer:
    """
    Sanitizer for log messages to prevent log injection
    """

    # Sensitive field names to redact
    SENSITIVE_FIELDS = [
        'password', 'secret', 'token', 'api_key', 'apikey',
        'authorization', 'auth', 'credential', 'credit_card',
        'card_number', 'cvv', 'ssn', 'private_key'
    ]

    @classmethod
    def sanitize_log_message(cls, message: str) -> str:
        """
        Sanitize log message to prevent log injection
        """
        if not message:
            return ""

        # Remove newlines to prevent log injection
        message = message.replace('\n', ' ').replace('\r', ' ')

        # Remove ANSI escape codes
        message = re.sub(r'\x1b\[[0-9;]*m', '', message)

        # Limit length
        if len(message) > 1000:
            message = message[:1000] + "..."

        return message

    @classmethod
    def sanitize_log_data(cls, data: dict) -> dict:
        """
        Sanitize dictionary for logging, redacting sensitive fields
        """
        if not isinstance(data, dict):
            return {}

        sanitized = {}
        for key, value in data.items():
            key_lower = key.lower()

            # Check if sensitive
            if any(sensitive in key_lower for sensitive in cls.SENSITIVE_FIELDS):
                sanitized[key] = "[REDACTED]"
            elif isinstance(value, dict):
                sanitized[key] = cls.sanitize_log_data(value)
            elif isinstance(value, str):
                sanitized[key] = cls.sanitize_log_message(value)
            else:
                sanitized[key] = value

        return sanitized


class DatabaseInputSanitizer:
    """
    Sanitizer for database inputs (defense in depth - always use parameterized queries)
    """

    @classmethod
    def sanitize_like_pattern(cls, pattern: str) -> str:
        """
        Sanitize LIKE pattern to escape special characters
        """
        if not pattern:
            return ""

        # Escape LIKE special characters
        pattern = pattern.replace('\\', '\\\\')
        pattern = pattern.replace('%', '\\%')
        pattern = pattern.replace('_', '\\_')

        return pattern

    @classmethod
    def sanitize_order_by(cls, column: str, allowed_columns: List[str]) -> Optional[str]:
        """
        Validate and sanitize ORDER BY column
        """
        if not column:
            return None

        column = column.strip().lower()

        if column not in [c.lower() for c in allowed_columns]:
            return None

        # Return the original case from allowed list
        for allowed in allowed_columns:
            if allowed.lower() == column:
                return allowed

        return None

    @classmethod
    def sanitize_limit(cls, limit: Any, max_limit: int = 100) -> int:
        """
        Sanitize LIMIT value
        """
        try:
            limit = int(limit)
        except (ValueError, TypeError):
            return 20  # Default

        if limit < 1:
            return 1

        if limit > max_limit:
            return max_limit

        return limit

    @classmethod
    def sanitize_offset(cls, offset: Any, max_offset: int = 10000) -> int:
        """
        Sanitize OFFSET value
        """
        try:
            offset = int(offset)
        except (ValueError, TypeError):
            return 0  # Default

        if offset < 0:
            return 0

        if offset > max_offset:
            return max_offset

        return offset
