"""Utility functions for JSON serialization, validation, and data manipulation.

Provides helpers for custom JSON encoding, nested field validation,
calculated data mapping, and serializer field introspection.
"""

from collections.abc import Callable, Mapping
import json
from datetime import date, datetime, time
from decimal import Decimal
from rest_framework.settings import api_settings
from rest_framework.exceptions import ValidationError
from rest_framework.fields import get_error_detail
from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.encoding import force_str
from django.utils.functional import Promise

ISO_DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"


def json_handler(obj):
    """Custom JSON serializer for handling various data types.

    Handles callables, objects with ``to_json()`` methods, sets, datetimes,
    dates, times, Decimals, and Django lazy translation strings.

    Args:
        obj: The object to serialize.

    Returns:
        A JSON-serializable representation of the object, or None on failure.
    """
    try:
        # Handle callable objects
        if isinstance(obj, Callable):
            obj = obj()

        # Custom serialization for specific types
        handlers = [
            (lambda x: callable(getattr(x, "to_json", None)), lambda x: x.to_json()),
            (lambda x: isinstance(x, set), list),
            (
                lambda x: callable(getattr(x, "as_dict", None)),
                lambda x: x.as_dict(),
            ),
            (
                lambda x: isinstance(x, datetime),
                lambda x: x.strftime(ISO_DATETIME_FORMAT),
            ),
            (lambda x: isinstance(x, date), lambda x: x.isoformat()),
            (lambda x: isinstance(x, time), lambda x: x.strftime("%H:%M:%S")),
            (lambda x: isinstance(x, Decimal), float),  # Warning: Precision loss
            (lambda x: isinstance(x, Promise), force_str),  # For lazy translations
        ]

        for condition, action in handlers:
            if condition(obj):
                return action(obj)

        # Fallback to the default JSONEncoder
        return json.JSONEncoder().default(obj)

    except Exception:
        return None


def is_any_nested_item_in_list(items, check_list):
    """Check if any item from ``items`` exists in ``check_list``.

    Args:
        items: Iterable of items to search for.
        check_list: Container to check against.

    Returns:
        True if any item is found in check_list, False otherwise.
    """
    return any(item in check_list for item in items)


def to_raw_dict(data):
    """Convert a non-dict object to a plain dict via JSON round-trip.

    Uses :func:`json_handler` for custom serialization of special types.

    Args:
        data: The data to convert. If already a dict, returned as-is.

    Returns:
        A plain dictionary representation of the data.
    """
    return (
        json.loads(json.dumps(data, default=json_handler))
        if not isinstance(data, dict)
        else data
    )


def map_calculated_data(path, result, data, many=False):
    """Map a calculated result back into a nested data structure.

    Args:
        path: Dot-separated path where the result should be placed.
        result: The calculated value to insert.
        data: The data structure to update.
        many: If True, result is a dict of index->value pairs for list items.

    Returns:
        The updated data structure.
    """
    primary_path, *secondary_path = path.split(".")
    if not secondary_path:
        data[primary_path] = result
    else:
        secondary_path = secondary_path[0]
        if many and isinstance(result, dict):
            for index, sub_result in result.items():
                data[primary_path][index][secondary_path] = sub_result
        else:
            data[primary_path][secondary_path] = result
    return data


def validation_error_detail(exc):
    """Extract structured error details from validation exceptions.

    Handles DRF ``ValidationError``, Django ``ValidationError``, dicts,
    and lists of errors, normalizing them into a consistent format.

    Args:
        exc: The validation error or error data structure.

    Returns:
        Structured error details suitable for API responses.
    """
    if isinstance(exc, ValidationError):
        return exc.detail

    if isinstance(exc, DjangoValidationError):
        return get_error_detail(exc)

    if isinstance(exc, Mapping):
        return {key: validation_error_detail(value) for key, value in exc.items()}
    elif isinstance(exc, list):
        return {exc.index(value): validation_error_detail(value) for value in exc}

    return {api_settings.NON_FIELD_ERRORS_KEY: [exc]}


def get_fields_config(fields):
    """Return the fields configuration as a dictionary.

    Introspects serializer fields to extract their type, allowed arguments,
    and extra kwargs for serialization/deserialization.

    Args:
        fields: Dict of field_name -> field instances to introspect.

    Returns:
        Dict mapping field names to their configuration dictionaries.
    """
    fields_config = {}
    for field_name, field in (fields or {}).items():
        allowed_args = getattr(field, "static_args", ()) + getattr(
            field, "extra_args", ()
        )
        allowed_kwargs = {
            attr: getattr(field, attr) for attr in dir(field) if attr in allowed_args
        }
        fields_config[field_name] = {
            "field_name": field_name,
            "type": field.__class__.__name__,
            **allowed_kwargs,
            **getattr(field, "extra_kwargs", {}),
        }

        if hasattr(field, "fields"):
            fields_config[field_name]["section"].update(
                {"fields": get_fields_config(field.fields)}
            )

    return fields_config
