"""Utility functions for FraiseQL - denormalized column naming conventions.

This module provides utilities for automatically generating denormalized column
names from nested field paths, enabling database-driven performance optimizations
without requiring application code changes.

Key Features:
- Converts nested paths (e.g., "location.ltreePath") to column names
  ("location__ltree_path") following a consistent naming convention
- Handles PostgreSQL's 63-byte column name limit with hash suffixes
- Deterministic naming ensures consistent column references
- Zero application code changes needed for database optimizations
"""

import hashlib
import re


def snake_case(s: str) -> str:
    """Convert camelCase to snake_case.

    Handles various naming conventions:
    - CamelCase: converts to snake_case
    - SCREAMING_SNAKE_CASE: converts to snake_case
    - Already snake_case: returns unchanged
    - Mixed styles: normalizes to snake_case

    Args:
        s: String to convert (may be camelCase, snake_case, or mixed)

    Returns:
        String converted to snake_case (lowercase with underscores)

    Examples:
        >>> snake_case("ltreePath")
        'ltree_path'
        >>> snake_case("HTTPServer")
        'http_server'
        >>> snake_case("field")
        'field'
        >>> snake_case("my_Field")
        'my_field'
    """
    if not s:
        return s

    result = s
    # Insert underscore before uppercase letters preceded by lowercase or digit
    result = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", result)
    # Insert underscore before uppercase letters preceded by lowercase or digit
    result = re.sub("([a-z0-9])([A-Z])", r"\1_\2", result)
    # Remove any double underscores that may have been created
    result = re.sub("_+", "_", result)
    # Convert to lowercase
    return result.lower()


def generate_denormalized_column_name(entity_path: str) -> str:
    """Generate denormalized column name from nested field path.

    Converts dot-separated nested field paths to PostgreSQL-compatible column
    names following the hierarchical naming convention:

        {entity_name}__{sub_entity}__{field_name}

    The function handles:
    1. Dot-to-double-underscore conversion (e.g., "." → "__")
    2. CamelCase to snake_case conversion for all path components
    3. PostgreSQL 63-byte column name limit with collision-safe hash suffix

    Hash Suffix Strategy (for paths > 63 bytes):
    - Generates a 6-character SHA256 hash of the full entity path
    - Truncates the readable portion to fit the 63-byte limit
    - Format: {readable_prefix}_{hash_suffix}
    - Ensures uniqueness: different paths always get different hash suffixes
    - Deterministic: same path always generates same column name

    Args:
        entity_path: Dot-separated path like "location.ltreePath" or "address.postalCode"

    Returns:
        Denormalized column name, guaranteed ≤ 63 bytes and valid for PostgreSQL

    Raises:
        TypeError: If entity_path is None

    Examples:
        >>> # Simple two-level nesting
        >>> generate_denormalized_column_name("location.ltreePath")
        'location__ltree_path'

        >>> # Multiple nested levels
        >>> generate_denormalized_column_name("company.dept.division.section")
        'company__dept__division__section'

        >>> # Preserves already snake_case components
        >>> generate_denormalized_column_name("user.address.postal_code")
        'user__address__postal_code'

        >>> # Deep nesting with hash suffix (if needed)
        >>> path = "very.deeply.nested.structure.with.many.levels.field"
        >>> col = generate_denormalized_column_name(path)
        >>> len(col.encode())  # Always ≤ 63 bytes
        56
    """
    if entity_path is None:
        raise TypeError("entity_path cannot be None")
    if not entity_path:
        return ""

    # Split path on dots and convert each component to snake_case
    parts = entity_path.split(".")
    snake_parts = [snake_case(part) for part in parts]
    tentative = "__".join(snake_parts)

    # If within PostgreSQL's 63-byte column name limit, return as-is
    if len(tentative.encode()) <= 63:
        return tentative

    # For longer names, use hash suffix to ensure uniqueness and collision safety
    # Generate SHA256 hash of the original path for consistency
    hash_suffix = hashlib.sha256(entity_path.encode()).hexdigest()[:6]
    # Reserve 1 byte for underscore separator + hash length
    max_readable = 63 - 1 - len(hash_suffix)
    readable = tentative[:max_readable]
    return f"{readable}_{hash_suffix}"


def parse_denormalized_column_name(column_name: str) -> str | None:
    """Reverse operation: detect if a column name is hash-suffixed denormalized column.

    This function identifies whether a column name was generated by
    generate_denormalized_column_name() and includes a hash suffix.

    Current Implementation:
    - Always returns None (placeholder for future enhancement)
    - Future: Could maintain a mapping to recover original entity path

    Args:
        column_name: Column name to analyze

    Returns:
        Original entity path if hash suffix detected and mapped, None otherwise

    Notes:
        This is a placeholder function for future enhancement. In a future version,
        we may implement bidirectional mapping where we can recover the original
        entity path from a hash-suffixed column name. Currently, the hash serves
        as a collision-safety mechanism without reverse lookup capability.
    """
    # Future implementation would check for patterns like: {name}_{6_hex_chars}
    # and maintain a registry to map back to the original entity path
    return None
