"""Serialization utilities for telemetry data capture.

This module provides safe JSON serialization for telemetry data with fallback
handling for non-serializable types and truncation for large payloads.
"""

import json
import logging
from typing import Any

logger = logging.getLogger(__name__)


def serialize_for_telemetry(
    data: Any,
    max_length: int = 1048576,  # 1MB limit for large payloads
    truncate_marker: str = "...[truncated]",
) -> str:
    """Serialize data to JSON-safe string for telemetry.

    Handles basic types, NumPy arrays, Pandas DataFrames, and custom objects
    with graceful fallback to string representation. Truncates large payloads
    to prevent excessive telemetry data.

    Args:
        data: Data to serialize (any type)
        max_length: Maximum length of serialized string (default: 1MB/1048576 bytes)
        truncate_marker: Marker to append when truncating (default: "...[truncated]")

    Returns:
        JSON string representation or str() fallback, truncated if necessary

    Examples:
        >>> serialize_for_telemetry({"key": "value"})
        '{"key": "value"}'

        >>> serialize_for_telemetry([1, 2, 3])
        '[1, 2, 3]'

        >>> serialize_for_telemetry("a" * 2000, max_length=100)
        'aaa...aaa...[truncated]'
    """
    try:
        # Custom JSON encoder to handle special types
        serialized = json.dumps(data, default=_json_default, ensure_ascii=False)
    except (TypeError, ValueError, OverflowError):
        # Fallback to string representation for non-serializable objects
        serialized = str(data)

    # Truncate if exceeds max length
    if len(serialized) > max_length:
        # Keep some content from the beginning and add truncation marker
        keep_length = max_length - len(truncate_marker)
        if keep_length > 0:
            serialized = serialized[:keep_length] + truncate_marker
        else:
            serialized = truncate_marker

    return serialized


def _json_default(obj: Any) -> Any:
    """Custom JSON encoder default handler for special types.

    Args:
        obj: Object that couldn't be serialized by default JSON encoder

    Returns:
        JSON-serializable representation of the object

    Raises:
        TypeError: If object cannot be converted to JSON-serializable form
    """
    obj_type = type(obj).__name__

    # Handle NumPy arrays
    if hasattr(obj, "tolist"):  # NumPy arrays have tolist() method
        try:
            result = obj.tolist()
            logger.debug(f"Serialized {obj_type} using tolist() method")
            return result
        except (AttributeError, TypeError, ValueError) as e:
            logger.warning(
                f"Failed to serialize {obj_type} using tolist(): {type(e).__name__}. "
                f"Trying next fallback method."
            )

    # Handle Pandas DataFrames/Series
    if hasattr(obj, "to_dict"):  # Pandas DataFrames/Series have to_dict() method
        try:
            result = obj.to_dict()
            logger.debug(f"Serialized {obj_type} using to_dict() method")
            return result
        except (AttributeError, TypeError, ValueError) as e:
            logger.warning(
                f"Failed to serialize {obj_type} using to_dict(): {type(e).__name__}. "
                f"Trying next fallback method."
            )

    # Handle dataclasses
    if hasattr(obj, "__dataclass_fields__"):
        try:
            result = {field: getattr(obj, field) for field in obj.__dataclass_fields__}
            logger.debug(f"Serialized dataclass {obj_type} using __dataclass_fields__")
            return result
        except (AttributeError, TypeError, ValueError) as e:
            logger.warning(
                f"Failed to serialize dataclass {obj_type}: {type(e).__name__}. "
                f"Trying next fallback method."
            )

    # Handle objects with __dict__ (custom classes)
    if hasattr(obj, "__dict__"):
        try:
            result = obj.__dict__
            logger.debug(f"Serialized {obj_type} using __dict__ attribute")
            return result
        except (AttributeError, TypeError, ValueError) as e:
            logger.warning(
                f"Failed to serialize {obj_type} using __dict__: {type(e).__name__}. "
                f"Using str() as final fallback."
            )

    # Final fallback: convert to string
    logger.debug(f"Serialized {obj_type} using str() fallback")
    return str(obj)
