"""
I/O sanitization, size measurement, and truncation utilities.

Provides a single function that sanitizes data for JSON serialization,
measures its size, and truncates if it exceeds the limit.
"""

import json
import uuid
from datetime import datetime
from typing import Any, Tuple


# Default max payload size per field (50 KB)
DEFAULT_MAX_BYTES = 50_000


def sanitize_and_measure(
    data: Any,
    max_bytes: int = DEFAULT_MAX_BYTES,
) -> Tuple[Any, int]:
    """Sanitize data for JSON, measure original size, truncate if needed.

    Converts UUID, datetime, bytes, and other non-JSON types to strings.
    Measures the JSON-encoded size of the original data. If it exceeds
    ``max_bytes``, the serialized form is truncated and wrapped.

    Args:
        data: Arbitrary data to sanitize.
        max_bytes: Maximum allowed size in bytes for the serialized form.

    Returns:
        (sanitized_data, original_size_bytes)
    """
    sanitized = _sanitize(data)

    try:
        encoded = json.dumps(sanitized, default=str)
        original_size = len(encoded.encode("utf-8"))
    except (TypeError, ValueError):
        encoded = str(sanitized)
        original_size = len(encoded.encode("utf-8"))

    if original_size <= max_bytes:
        return sanitized, original_size

    # Truncate: return a summary dict instead of the full payload
    truncated = {
        "_truncated": True,
        "_original_size_bytes": original_size,
        "_preview": encoded[:max_bytes].rstrip(),
    }
    return truncated, original_size


def _sanitize(obj: Any) -> Any:
    """Recursively convert non-JSON-serializable objects to strings."""
    if isinstance(obj, dict):
        return {str(k): _sanitize(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple)):
        return [_sanitize(v) for v in obj]
    if isinstance(obj, uuid.UUID):
        return str(obj)
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, bytes):
        return obj.decode("utf-8", errors="replace")
    if not isinstance(obj, (str, int, float, bool, type(None))):
        return str(obj)
    return obj
