"""Input sanitization and validation utilities.

Provides validation and sanitization for common input types to prevent
injection attacks and ensure data integrity.
"""

from __future__ import annotations

import re
import uuid
from typing import Any

# Email regex (RFC 5322 simplified)
_EMAIL_PATTERN = re.compile(
    r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
)

# Slug pattern (URL-safe identifiers)
_SLUG_PATTERN = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")

# UUID pattern (RFC 4122)
_UUID_PATTERN = re.compile(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE)


class InputSanitizer:
    """Input sanitization utilities.

    This class provides static methods for validating and sanitizing various
    input types. All methods raise ValueError on invalid input.
    """

    @staticmethod
    def sanitize_email(email: str) -> str:
        """Validate and normalize an email address.

        Parameters
        ----------
        email:
            Email address to validate.

        Returns
        -------
        str
            Normalized email (lowercased, stripped).

        Raises
        ------
        ValueError:
            If email format is invalid.
        """
        if not email:
            raise ValueError("Email cannot be empty")

        email = email.strip().lower()

        if len(email) > 254:  # RFC 5321
            raise ValueError("Email address too long")

        if not _EMAIL_PATTERN.match(email):
            raise ValueError("Invalid email format")

        return email

    @staticmethod
    def sanitize_slug(slug: str, max_length: int = 100) -> str:
        """Validate a URL slug.

        Parameters
        ----------
        slug:
            Slug to validate (lowercase letters, numbers, hyphens).
        max_length:
            Maximum allowed length.

        Returns
        -------
        str
            The validated slug.

        Raises
        ------
        ValueError:
            If slug format is invalid.
        """
        if not slug:
            raise ValueError("Slug cannot be empty")

        slug = slug.strip().lower()

        if len(slug) > max_length:
            raise ValueError(f"Slug too long (max {max_length} characters)")

        if not _SLUG_PATTERN.match(slug):
            raise ValueError("Invalid slug format (use lowercase letters, numbers, and hyphens)")

        return slug

    @staticmethod
    def sanitize_uuid(value: str) -> str:
        """Validate a UUID string.

        Parameters
        ----------
        value:
            UUID string to validate.

        Returns
        -------
        str
            Normalized UUID (lowercase).

        Raises
        ------
        ValueError:
            If UUID format is invalid.
        """
        if not value:
            raise ValueError("UUID cannot be empty")

        value = value.strip().lower()

        if not _UUID_PATTERN.match(value):
            raise ValueError("Invalid UUID format")

        # Validate using uuid module for additional checks
        try:
            uuid.UUID(value)
        except ValueError as exc:
            raise ValueError("Invalid UUID") from exc

        return value

    @staticmethod
    def sanitize_string(
        value: str,
        min_length: int = 0,
        max_length: int = 1000,
        allow_newlines: bool = True,
    ) -> str:
        """Sanitize a general string input.

        Parameters
        ----------
        value:
            String to sanitize.
        min_length:
            Minimum allowed length.
        max_length:
            Maximum allowed length.
        allow_newlines:
            Whether to allow newline characters.

        Returns
        -------
        str
            Sanitized string (stripped, length validated).

        Raises
        ------
        ValueError:
            If string fails validation.
        """
        if not isinstance(value, str):
            raise ValueError("Value must be a string")

        # Strip whitespace
        value = value.strip()

        if len(value) < min_length:
            raise ValueError(f"String too short (min {min_length} characters)")

        if len(value) > max_length:
            raise ValueError(f"String too long (max {max_length} characters)")

        if not allow_newlines and ("\n" in value or "\r" in value):
            raise ValueError("Newlines not allowed")

        return value

    @staticmethod
    def sanitize_dict(data: dict[str, Any], allowed_keys: set[str] | None = None) -> dict[str, Any]:
        """Sanitize a dictionary by filtering keys.

        Parameters
        ----------
        data:
            Dictionary to sanitize.
        allowed_keys:
            Set of allowed keys. If None, all keys are allowed.

        Returns
        -------
        dict[str, Any]
            Sanitized dictionary with only allowed keys.

        Raises
        ------
        ValueError:
            If data is not a dictionary.
        """
        if not isinstance(data, dict):
            raise ValueError("Value must be a dictionary")

        if allowed_keys is None:
            return data

        return {k: v for k, v in data.items() if k in allowed_keys}


# Convenience functions for direct use
def sanitize_email(email: str) -> str:
    """Validate and normalize an email address."""
    return InputSanitizer.sanitize_email(email)


def sanitize_slug(slug: str, max_length: int = 100) -> str:
    """Validate a URL slug."""
    return InputSanitizer.sanitize_slug(slug, max_length)


def sanitize_uuid(value: str) -> str:
    """Validate a UUID string."""
    return InputSanitizer.sanitize_uuid(value)


def sanitize_string(
    value: str,
    min_length: int = 0,
    max_length: int = 1000,
    allow_newlines: bool = True,
) -> str:
    """Sanitize a general string input."""
    return InputSanitizer.sanitize_string(value, min_length, max_length, allow_newlines)
