"""Centralized hash utilities for consistent, deterministic, and secure hashing.

This module provides deterministic hash functions that are safe for use in:
- Cache keys (consistent across Python processes/sessions)
- UUID generation from strings
- Unique identifiers that need to be stable

IMPORTANT: Python's built-in hash() function is NOT deterministic across
Python sessions (PYTHONHASHSEED varies). Use these utilities instead.

Security Notes:
- SHA-256 is used by default for new implementations
- MD5 is deprecated and should only be used for legacy compatibility
- These hashes are NOT cryptographic - do not use for passwords or security tokens
"""

import hashlib
from uuid import UUID


def deterministic_hash(value: str) -> str:
    """Generate a deterministic hash string using SHA-256.

    This function produces consistent output across Python sessions,
    unlike the built-in hash() function which varies with PYTHONHASHSEED.

    Args:
        value: The string to hash

    Returns:
        A hexadecimal hash string (64 characters)

    Example:
        >>> deterministic_hash("my_cache_key")
        '4f7af13edd932a4d13660d4a49003e7d6e258c4fd35917b472f971058e2f06f5'
    """
    return hashlib.sha256(value.encode()).hexdigest()


def deterministic_hash_int(value: str) -> int:
    """Generate a deterministic integer hash using SHA-256.

    This function produces consistent integer output across Python sessions,
    suitable for modular arithmetic (e.g., selecting from a list of nodes).

    Args:
        value: The string to hash

    Returns:
        A positive integer derived from the hash

    Example:
        >>> node_index = deterministic_hash_int("user_session_123") % len(nodes)
    """
    return int(hashlib.sha256(value.encode()).hexdigest(), 16)


def deterministic_cache_key(*args: object, **kwargs: object) -> str:
    """Generate a deterministic cache key from arguments.

    Creates a stable cache key by hashing the string representation
    of arguments in a deterministic way.

    Args:
        *args: Positional arguments to include in the key
        **kwargs: Keyword arguments to include in the key

    Returns:
        A hexadecimal hash string suitable for cache keys

    Example:
        >>> key = deterministic_cache_key("func_name", (1, 2, 3), foo="bar")
    """
    # Sort kwargs for deterministic ordering
    sorted_kwargs = sorted(kwargs.items())
    key_str = f"{args}_{sorted_kwargs}"
    return hashlib.sha256(key_str.encode()).hexdigest()


def string_to_uuid(value: str) -> UUID:
    """Convert a string to a deterministic UUID using SHA-256.

    This creates a valid UUID from any string input, producing
    consistent results across Python sessions.

    The UUID is generated by taking the first 32 hex characters
    of the SHA-256 hash and formatting as a UUID.

    Args:
        value: The string to convert to UUID

    Returns:
        A UUID derived from the string

    Example:
        >>> uuid = string_to_uuid("my_dependency_name")
        >>> str(uuid)
        'e3b0c442-98fc-1c14-9afb-f4c899....'
    """
    hash_hex = hashlib.sha256(value.encode()).hexdigest()[:32]
    return UUID(hash_hex)


def deterministic_jitter(
    seed: str, base_delay: float, jitter_factor: float = 0.1
) -> float:
    """Calculate deterministic jitter for retry delays.

    Uses a deterministic hash to add jitter to delays, avoiding
    thundering herd problems while maintaining reproducibility.

    Args:
        seed: A seed string (e.g., timestamp or request ID)
        base_delay: The base delay in seconds
        jitter_factor: Maximum jitter as a fraction of base_delay (default 0.1 = 10%)

    Returns:
        Jitter value to add to the delay (can be negative)

    Example:
        >>> jitter = deterministic_jitter(str(time.time()), 1.0, 0.1)
        >>> delay = base_delay + jitter
    """
    # Get a value between 0 and 1
    hash_int = deterministic_hash_int(seed)
    normalized = (hash_int % 1000) / 1000.0  # 0.0 to 0.999
    # Convert to range -1.0 to 1.0, then scale by jitter_factor
    # This gives jitter in range [-jitter_factor * base_delay, +jitter_factor * base_delay]
    jitter = base_delay * jitter_factor * (2.0 * normalized - 1.0)
    return jitter


def deterministic_error_code(error_message: str) -> float:
    """Generate a deterministic error code from an error message.

    Creates a stable error code (0.0 to 1.0) from an error message,
    suitable for health metrics or error categorization.

    Args:
        error_message: The error message to hash

    Returns:
        A float between 0.0 and 1.0

    Example:
        >>> code = deterministic_error_code("Connection refused")
        >>> 0.0 <= code <= 1.0
        True
    """
    hash_int = deterministic_hash_int(error_message)
    return (hash_int % 1000) / 1000.0
