"""
Core type definitions for payload-guard.
Provides strong type inference from shape definitions.
"""

from __future__ import annotations
from typing import Any, Callable, Protocol, TypedDict, Literal, Union, Optional
from dataclasses import dataclass, field


# Primitive type strings
PrimitiveType = Literal["string", "number", "boolean", "any"]


@dataclass
class ValidationError:
    """Validation error structure."""

    field: str
    message: str
    code: Optional[str] = None
    value: Optional[Any] = None

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        result = {"field": self.field, "message": self.message}
        if self.code:
            result["code"] = self.code
        if self.value is not None:
            result["value"] = self.value
        return result


@dataclass
class ValidationResult:
    """Validation result with errors."""

    data: Any
    errors: list[ValidationError] = field(default_factory=list)

    @property
    def valid(self) -> bool:
        """Check if validation passed."""
        return len(self.errors) == 0

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "data": self.data,
            "errors": [e.to_dict() for e in self.errors],
            "valid": self.valid,
        }


@dataclass
class ErrorCodes:
    """Error codes for different validation failures."""

    required: Optional[str] = None
    type: Optional[str] = None
    min: Optional[str] = None
    max: Optional[str] = None
    pattern: Optional[str] = None
    email: Optional[str] = None
    custom: Optional[str] = None


# Error message type - can be string or callable
ErrorMessage = Union[str, Callable[[Any, str], str]]


@dataclass
class FieldConfig:
    """Field configuration with options."""

    type: PrimitiveType = "any"
    required: bool = False
    default: Optional[Any] = None
    min: Optional[Union[int, float]] = None
    max: Optional[Union[int, float]] = None
    regex: Optional[str] = None  # Pattern string
    email: bool = False
    transform: Optional[Callable[[Any], Any]] = None
    from_field: Optional[str] = None  # Source field key name (aliasing)
    validate: Optional[Callable[[Any], bool]] = None
    error_message: Optional[ErrorMessage] = None
    error_codes: Optional[ErrorCodes] = None
    shape: Optional[ShapeDescriptor] = None  # Nested shape for object validation

    def to_dict(self) -> dict:
        """Convert to dictionary."""
        return {
            "type": self.type,
            "required": self.required,
            "default": self.default,
            "min": self.min,
            "max": self.max,
            "regex": self.regex,
            "email": self.email,
            "from_field": self.from_field,
        }


# Shape descriptor can be:
# - A primitive type string
# - A field config object
# - A nested object shape (dict)
# - A compiled filter function
ShapeDescriptor = Union[
    PrimitiveType,
    FieldConfig,
    dict[str, "ShapeDescriptor"],
    Callable[[Any], Any],
    "ShapeBuilder",
]


class ShapeBuilder(Protocol):
    """Builder interface for chained configuration."""

    config: FieldConfig

    def __call__(self, value: Any) -> Any:
        """Call the builder."""
        ...

    def required(self) -> ShapeBuilder:
        """Mark field as required."""
        ...

    def optional(self) -> ShapeBuilder:
        """Mark field as optional."""
        ...

    def default(self, val: Any) -> ShapeBuilder:
        """Set default value."""
        ...

    def transform(self, fn: Callable[[Any], Any]) -> ShapeBuilder:
        """Add transformation function."""
        ...

    def validate(self, fn: Callable[[Any], bool]) -> ShapeBuilder:
        """Add validation function."""
        ...

    def from_(self, key: str) -> ShapeBuilder:
        """Set source field name (aliasing)."""
        ...

    def error(self, msg: ErrorMessage) -> ShapeBuilder:
        """Set custom error message."""
        ...

    def error_codes(self, codes: ErrorCodes) -> ShapeBuilder:
        """Set error codes."""
        ...


class StringBuilder(ShapeBuilder, Protocol):
    """String builder with chained constraints."""

    def min(self, length: int) -> StringBuilder:
        """Set minimum length."""
        ...

    def max(self, length: int) -> StringBuilder:
        """Set maximum length."""
        ...

    def email(self) -> StringBuilder:
        """Validate as email."""
        ...

    def regex(self, pattern: str) -> StringBuilder:
        """Validate with regex pattern."""
        ...

    def trim(self) -> StringBuilder:
        """Auto-trim whitespace."""
        ...

    def to_lower(self) -> StringBuilder:
        """Convert to lowercase."""
        ...

    def to_upper(self) -> StringBuilder:
        """Convert to uppercase."""
        ...


class NumberBuilder(ShapeBuilder, Protocol):
    """Number builder with chained constraints."""

    def min(self, value: Union[int, float]) -> NumberBuilder:
        """Set minimum value."""
        ...

    def max(self, value: Union[int, float]) -> NumberBuilder:
        """Set maximum value."""
        ...

    def integer(self) -> NumberBuilder:
        """Ensure number is an integer."""
        ...

    def positive(self) -> NumberBuilder:
        """Ensure number is positive."""
        ...


class BooleanBuilder(ShapeBuilder, Protocol):
    """Boolean builder."""

    ...


class AnyBuilder(ShapeBuilder, Protocol):
    """Any type builder."""

    ...


@dataclass
class GuardConfig:
    """Guard configuration options."""

    # Additional sensitive field names to filter
    sensitive_fields: list[str] = field(default_factory=list)
    # Enable development mode warnings
    dev_mode: bool = False
    # Log warnings to console
    log_warnings: bool = True
    # Strict mode: raise on validation/type errors
    strict: bool = False
    # Redaction hook: return replacement value for a key
    redact: Optional[Callable[[str, Any], Any]] = None
    # Logger integration hook
    logger: Optional[Callable[[str, str], None]] = None
    # Maximum payload size (bytes) for middleware processing
    max_payload_size: Optional[int] = None
    # Maximum array length to process (memory safety)
    max_array_length: Optional[int] = None
    # Routes to ignore (prefix match)
    ignore_routes: Optional[list[str]] = None
    # Enable payload metrics
    payload_metrics: bool = False
    # Sanitize request headers
    sanitize_headers: bool = False
    # Sensitive header names to filter
    sensitive_headers: list[str] = field(
        default_factory=lambda: ["authorization", "cookie", "x-api-key"]
    )
    # Fail-safe mode: if filtering fails, return original data
    fail_open: bool = True
    # Skip filtering for non-serializable objects
    skip_non_serializable: bool = True
    # Log names of fields removed during filtering
    log_removed_fields: bool = False
    # Collect validation errors instead of silently failing
    collect_errors: bool = False


# Compiled filter function type
CompiledFilter = Callable[[Any], Any]


# Middleware options
@dataclass
class GuardMiddlewareOptions:
    """Middleware options for web frameworks."""

    # Sanitize request body
    sanitize_body: bool = True
    # Shape for request body sanitization
    request_shape: Optional[ShapeDescriptor] = None
    # Filter all responses automatically
    filter_response: bool = False
    # Response shape (optional)
    response_shape: Optional[ShapeDescriptor] = None
    # Additional guard config options
    sensitive_fields: Optional[list[str]] = None
    dev_mode: Optional[bool] = None
    strict: Optional[bool] = None
    max_array_length: Optional[int] = None
    collect_errors: Optional[bool] = None
