"""
Defines the foundational primitives for strict, immutable data modeling.

This module establishes the canonical type system for the core.
Its purpose is to provide the atomic building blocks and security boundaries
for the domain.

It focuses on two core concerns:

1. Data Integrity
The `CanonicalRecord` base class enforces immutability (frozen instances)
and strict schema validation (no unknown fields), ensuring that once a
state exists, it cannot silently drift or degrade.

2. Defensive Semantic Typing
Specialized string types (`MachineID`, `HumanLabel`,
`SemanticText`, `ExternalID`, `MimeType`, etc.) act as explicit
semantic and security boundaries, preventing malformed, ambiguous,
or adversarial data from entering the system.
"""


from typing import Optional, Annotated, Dict, Any
from pydantic import BaseModel, ConfigDict, Field, AfterValidator
import re
import math
import json
import base64
import binascii


# ═══════════════════════════════════════════════════════════════════════════
# CONFIGURATION CONSTANTS
# ═══════════════════════════════════════════════════════════════════════════

MAX_IDENTIFIER_LENGTH = 128
"""Maximum allowed length for machine identifiers to prevent database overflow."""

MAX_LABEL_LENGTH = 256
"""Maximum allowed length for human-readable labels."""

MAX_SEMANTIC_TEXT_LENGTH = 10_000_000
"""Maximum allowed length for semantic text content to prevent DoS attacks (10MB)."""

MAX_EXTERNAL_ID_LENGTH = 256
"""Length sufficient for complex provider IDs (e.g., AWS ARNs, long UUIDs)."""

MIME_TYPE_REGEX = re.compile(r'^[a-z0-9\.\-\+]+/[a-z0-9\.\-\+]+(;.*)?$', re.IGNORECASE)
"""Regex for basic MIME type validation (type/subtype). Allows optional parameters."""

ID_REGEX = re.compile(r'^[a-z0-9]([a-z0-9_\-]*[a-z0-9])?$')
"""Regex pattern for validating machine identifiers."""

URL_REGEX = re.compile(
    r'^https?://'                                                    # Scheme
    r'(?:'
        r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,63}\.?' # Domain (2-63 chars TLD)
        r'|localhost'                                                # Localhost
        r'|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}'                       # IPv4
        r'|\[[0-9A-F:]+\]'                                           # IPv6 (Basic, requires brackets)
    r')'
    r'(?::\d+)?'                                                     # Port
    r'(?:/?|[/?]\S+)$',
    re.IGNORECASE
)

SEMVER_REGEX = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*)?$")
"""Regex for validating semantic versioning strings (e.g., '1.0.0', '2.1.0-beta')."""

CONTEXT_KEY_REGEX = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*$")
"""Regex for validating context variable names (must be valid Python identifiers)."""

MAX_NESTING_DEPTH = 20
"""Maximum allowed nesting depth for JSON-like structures to prevent stack overflow."""


# ═══════════════════════════════════════════════════════════════════════════
# BASE CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════

class CanonicalRecord(BaseModel):
    """A base record providing shared configuration for all canonical data structures.

    This abstract base class serves as the foundation for all domain models within
    the core. It enforces strict data validation rules to ensure that data
    passing through the system is immutable and predictable.

    Configuration Behaviors:
        - **Immutability (frozen=True):** Instances cannot be modified after creation. 
        This makes them thread-safe and hashable.
        - **Strictness (extra='forbid'):** The model will raise a validation error 
        if unknown fields are provided. This prevents silent failures caused by 
        typos or outdated API responses.

    Example:
        >>> class MyConfig(CanonicalRecord):
        ...     name: str
        ...
        >>> conf = MyConfig(name="test")    
        >>> conf.name = "changed"  # Raises ValidationError (Frozen)
        >>> conf = MyConfig(name="test", bad_field=1)  # Raises ValidationError (Extra)
    """
    model_config = ConfigDict(
        frozen=True,
        extra='forbid',
        use_enum_values=False
    )


# ═══════════════════════════════════════════════════════════════════════════
# FLOAT VALIDATION HELPERS
# ═══════════════════════════════════════════════════════════════════════════


def _validate_finite_float(v: Optional[float]) -> Optional[float]:
    """Validates that a float value is finite, non-negative, and not NaN.
    
    Args:
        v: The float value to validate.
    
    Returns:
        The validated float value.
    
    Raises:
        ValueError: If the value is NaN, Infinity, or negative.
    """
    if v is None:
        return v
    
    if math.isnan(v):
        raise ValueError("Value cannot be NaN. Must be a valid number.")
    
    if math.isinf(v):
        raise ValueError("Value cannot be Infinity. Must be finite.")
        
    if v < 0:
        raise ValueError("Value cannot be negative.")
        
    return v

# ═══════════════════════════════════════════════════════════════════════════
# FINITE POSITIVE FLOAT TYPE
# ═══════════════════════════════════════════════════════════════════════════

FinitePositiveFloat = Annotated[
    float,
    AfterValidator(_validate_finite_float),
    Field(description="Non-negative, finite floating point value.")
]


# ═══════════════════════════════════════════════════════════════════════════
# STRING NORMALIZATION HELPERS
# ═══════════════════════════════════════════════════════════════════════════

def _validate_machine_identifier(v: str) -> str:    
    """Validates and normalizes a machine identifier string.

    Enforces that the string is not empty or whitespace-only after stripping,
    and prevents DoS attacks by enforcing maximum length constraints.

    Args:
        v: The input string to validate.

    Returns:
        str: The stripped identifier string.

    Raises:
        ValueError: If the string is empty, contains only whitespace, or exceeds 
                    the maximum allowed length.
    """
    if v is None:
        return v
    v_stripped = v.strip()
    if not v_stripped:
        raise ValueError("MachineID cannot be empty or whitespace only")
    if len(v_stripped) > MAX_IDENTIFIER_LENGTH:
        raise ValueError(
            f"MachineID exceeds maximum length of {MAX_IDENTIFIER_LENGTH} "
            f"characters (got {len(v_stripped)})"
        )
    if not ID_REGEX.match(v_stripped):
        raise ValueError(
            f"Invalid identifier '{v_stripped}'. Must be lowercase, alphanumeric, "
            "and use only '-' or '_' as separators."
        )
    return v_stripped


def _validate_human_label(v: str) -> str:
    """Validates and normalizes a human-readable UI label.

    Enforces that the string is not empty, contains no newline characters,
    and does not exceed length limits. Also filters invisible control characters
    that could break UI rendering or JSON logs.

    Args:
        v: The input string to validate.

    Returns:
        str: The stripped, single-line label.

    Raises:
        ValueError: If the string is empty, whitespace-only, contains newlines,
                    contains invisible control characters, or exceeds maximum length.
    """
    if v is None:
        return v
    
    # Strip leading/trailing whitespace
    v = v.strip()
    
    if not v:
        raise ValueError("HumanLabel cannot be empty or whitespace only")
    
    # Check for newlines
    if '\n' in v or '\r' in v:
        raise ValueError("HumanLabel must be a single line (no newlines)")
    
    if not v.isprintable():
        raise ValueError(f"HumanLabel contains non-printable characters")
    
    # Check for additional invisible control characters (ASCII 0-31 except space)
    # This prevents tabs, vertical tabs, backspaces, etc.
    control_chars = [c for c in v if ord(c) < 32 and c not in ('\n', '\r')]
    if control_chars:
        raise ValueError(
            f"HumanLabel contains invisible control characters: "
            f"{[hex(ord(c)) for c in control_chars]}"
        )
    
    # Enforce stricter length limit for user-facing labels
    if len(v) > MAX_LABEL_LENGTH:
        raise ValueError(
            f"HumanLabel exceeds maximum length of {MAX_LABEL_LENGTH} "
            f"characters (got {len(v)})"
        )
    
    return v


def _validate_safe_url(v: str) -> str:
    """Validates that a string is a safe, absolute HTTP/HTTPS URL.
    
    Args:
        v: The URL string to validate.
    
    Returns:
        str: The validated URL string.
    
    Raises:
        ValueError: If the URL is empty, a data URI, a relative path, or invalid format.
    """
    if v is None:
        return v
    v = v.strip()
    
    if not v:
        raise ValueError("SafeURL cannot be empty")
    
    # Explicit prevention of Data URIs in the wrong field
    if v.lower().startswith("data:"):
        raise ValueError("Data URIs are not allowed in SafeURL. Use Base64Data instead.")
        
    # Prevention of relative paths (e.g., "/images/logo.png")
    if v.startswith("/"):
        raise ValueError("Relative URLs are not allowed. Must be absolute (http/https).")
        
    if not URL_REGEX.match(v):
        raise ValueError(f"Invalid URL format: '{v}'. Must be absolute HTTP/HTTPS.")
        
    return v


def _validate_base64_data(v: str) -> str:
    """Validates that a string is a valid Base64 encoded payload.
    
    Args:
        v: The Base64 string to validate.
    
    Returns:
        str: The validated and cleaned Base64 string.
    
    Raises:
        ValueError: If the string is empty, contains invalid characters, or is corrupted.
    """
    if v is None:
        return v
    
    # Basic cleaning (ignore spaces/newlines that break strict decoders)
    v_clean = re.sub(r'\s+', '', v)
    
    if not v_clean:
        raise ValueError("Base64Data cannot be empty")
        
    # Verify valid characters (A-Z, a-z, 0-9, +, /, =)
    if not re.fullmatch(r'[A-Za-z0-9+/]*={0,2}', v_clean):
        raise ValueError("Invalid Base64 characters detected. Only A-Z, a-z, 0-9, +, / are allowed.")
        
    # Verify actual decoding (structural integrity)
    try:
        # validate=True is important to reject incorrect padding
        base64.b64decode(v_clean, validate=True)
    except binascii.Error as e:
        raise ValueError(f"Corrupt Base64 data: {str(e)}")
        
    return v_clean


def _validate_semantic_text(v: str) -> str:
    """Validates semantic text content to prevent DoS attacks.

    Enforces maximum length constraints to prevent memory exhaustion attacks
    from unbounded string payloads. Semantic text is typically used for prompts,
    code, or other content where whitespace preservation is critical.

    Args:
        v: The input string to validate.

    Returns:
        str: The validated semantic text string.

    Raises:
        ValueError: If the string exceeds the maximum allowed length.
    """
    if v is None:
        return v
    
    if len(v) > MAX_SEMANTIC_TEXT_LENGTH:
        raise ValueError(
            f"SemanticText exceeds maximum length of {MAX_SEMANTIC_TEXT_LENGTH} "
            f"characters (got {len(v)}). This limit prevents DoS attacks from "
            f"unbounded string payloads."
        )
    
    return v


def _validate_external_id(v: str) -> str:
    """Validates identifiers generated by external providers (Postel's Law).
    
    Accepts a wider range of characters than MachineID but enforces
    safety constraints (printable characters, length).
    
    Args:
        v: The external ID string to validate.
    
    Returns:
        str: The validated external ID string.
    
    Raises:
        ValueError: If the ID is empty, too long, or contains unsafe characters.
    """
    if v is None:
        return v
    v_stripped = v.strip()
    
    if not v_stripped:
        raise ValueError("ExternalID cannot be empty or whitespace only")
        
    if len(v_stripped) > MAX_EXTERNAL_ID_LENGTH:
        raise ValueError(
            f"ExternalID exceeds max length of {MAX_EXTERNAL_ID_LENGTH}"
        )
    
    # Security: Ensure no invisible control characters (NULL bytes, etc.)
    if not v_stripped.isprintable():
        raise ValueError("ExternalID contains unsafe control characters")
        
    return v_stripped


def _validate_mime_type(v: str) -> str:
    """Validates that a string looks like a standard MIME type (type/subtype).
    
    Args:
        v: The MIME type string to validate.
    
    Returns:
        str: The validated MIME type string in lowercase.
    
    Raises:
        ValueError: If the MIME type is empty or has invalid format.
    """
    if v is None:
        return v
    # Preserve original case here; callers may enforce exact-case policies.
    v_stripped = v.strip().lower()
    
    if not v_stripped:
        raise ValueError("MimeType cannot be empty")
        
    if not MIME_TYPE_REGEX.match(v_stripped):
        raise ValueError(f"Invalid MIME type format: '{v}'. Expected 'type/subtype'.")
        
    return v_stripped


def _validate_semver(v: str) -> str:
    """Validates Semantic Versioning (Major.Minor.Patch[-PreRelease])."""
    if v is None:
        return v
    v_stripped = v.strip()
    if not SEMVER_REGEX.match(v_stripped):
        raise ValueError(f"Invalid Semantic Version format: '{v_stripped}'. Expected MAJOR.MINOR.PATCH.")
    return v_stripped


def _validate_context_key(v: str) -> str:
    """Validates a key used for programmatic context/memory storage."""
    if v is None:
        return v
    v_stripped = v.strip()
    if len(v_stripped) > 64:
        raise ValueError("ContextKey exceeds maximum length of 64 characters")
    if not CONTEXT_KEY_REGEX.match(v_stripped):
        raise ValueError(
            f"Invalid ContextKey '{v_stripped}'. "
            "Must be a valid programmatic identifier (letters, numbers, underscores)."
        )
    return v_stripped


def _validate_safe_payload(v: Dict[str, Any]) -> Dict[str, Any]:
    """Validates that a dictionary is strictly JSON-serializable and depth-bounded."""
    if not isinstance(v, dict):
        raise ValueError("Payload must be a dictionary.")
    
    # 1. Validar serialización física (Previene objetos de BD, lambdas, etc.)
    try:
        json.dumps(v)
    except (TypeError, ValueError) as e:
        raise ValueError(f"Payload must be strictly JSON-serializable: {e}")
    
    # 2. Validar profundidad recursiva (Previene JSON Bombs / Alias YAML)
    def _check_depth(data: Any, current_depth: int = 0):
        if current_depth > MAX_NESTING_DEPTH:
            raise ValueError(
                f"Data structure exceeds maximum nesting depth of {MAX_NESTING_DEPTH}."
            )
        if isinstance(data, dict):
            for value in data.values():
                _check_depth(value, current_depth + 1)
        elif isinstance(data, list):
            for item in data:
                _check_depth(item, current_depth + 1)
                
    _check_depth(v)
    return v




# ═══════════════════════════════════════════════════════════════════════════
# SEMANTIC PRIMITIVES
# ═══════════════════════════════════════════════════════════════════════════

MachineID = Annotated[
    str,
    AfterValidator(_validate_machine_identifier),
    Field(description="Machine identifier (stripped, non-empty, max 128 chars)")
]

SafeURL = Annotated[
    str,
    AfterValidator(_validate_safe_url),
    Field(description="Absolute HTTP/HTTPS URL (no data URIs, no relative paths)")
]

Base64Data = Annotated[
    str,
    AfterValidator(_validate_base64_data),
    Field(description="Valid Base64 encoded data string (whitespace stripped)")
]

HumanLabel = Annotated[
    str,
    AfterValidator(_validate_human_label),
    Field(description="Human-readable label (single line, max 256 chars)")
]

SemanticText = Annotated[
    str,
    AfterValidator(_validate_semantic_text),
    Field(description="Raw semantic content (whitespace preserved, max 10MB)")
]


ExternalID = Annotated[
    str,
    AfterValidator(_validate_external_id),
    Field(description="External provider identifier (liberal validation, strict safety)")
]

MimeType = Annotated[
    str,
    AfterValidator(_validate_mime_type),
    Field(description="Standard MIME type string (e.g., 'image/png', 'application/json')")
]

SemanticVersion = Annotated[
    str,
    AfterValidator(_validate_semver),
    Field(description="Semantic version string (e.g., '1.0.0', '2.1.0-beta')")
]

ContextKey = Annotated[
    str,
    AfterValidator(_validate_context_key),
    Field(description="Safe programmatic key for state/memory storage")
]

SafePayload = Annotated[
    Dict[str, Any],
    AfterValidator(_validate_safe_payload),
    Field(description="A strictly JSON-serializable dictionary with bounded nesting depth.")
]