"""
Integration with django-readers.
"""

import sys
import typing
from decimal import Decimal
from typing import Callable

import attrs

# Python 3.10+ has types.UnionType for X | Y syntax
if sys.version_info >= (3, 10):
    import types

    UNION_TYPES = (typing.Union, types.UnionType)
else:
    UNION_TYPES = (typing.Union,)


def spec(custom_spec):
    """
    Decorator to specify a complete django-readers spec for an attrs class.

    Usage:
        @spec(['field1', {'field2': ['nested_field']}])
        @define
        class MyAttrs:
            field1: str
            field2: NestedAttrs

    This completely bypasses auto-generation in to_spec() and uses the provided spec.
    Useful for complex specs that aren't covered by the current auto-generation API.
    """

    def decorator(cls):
        if not attrs.has(cls):
            raise TypeError("spec decorator can only be applied to attrs classes")

        # Store the custom spec as a class attribute
        cls.__mantle_spec__ = custom_spec

        return cls

    return decorator


def overrides(
    field_overrides: dict[str, tuple[Callable, Callable]],
    allow_missing: list[str] | None = None,
):
    """
    Decorator to specify custom django-readers pairs for attrs fields.

    Usage:
        @overrides({"field_name": (prepare_fn, project_fn)})
        @define
        class MyAttrs:
            field_name: str

    The override pair will be used in place of auto-generated spec logic.

    Args:
        field_overrides: Dict mapping field names to (prepare, project) pairs.
        allow_missing: List of field names that are allowed to not exist in the attrs class.
                      Useful when cattrs is configured with a custom constructor that maps
                      incoming data to available fields.
    """

    def decorator(cls):
        if not attrs.has(cls):
            raise TypeError("overrides decorator can only be applied to attrs classes")

        # Validate all override fields exist (unless in allow_missing list)
        class_fields = {f.name for f in attrs.fields(cls)}
        allow_set = set(allow_missing or [])

        for field_name in field_overrides.keys():
            if field_name not in class_fields and field_name not in allow_set:
                raise ValueError(
                    f"Override specified for '{field_name}' but no such field exists "
                    "in {cls.__name__}"
                )

        # Store overrides as a class attribute that to_spec can access
        cls.__mantle_overrides__ = field_overrides

        return cls

    return decorator


def to_spec(attrs_cls):
    """
    Generate a django-readers spec from an attrs class.

    Supports:
    - Basic types: int, float, Decimal, str, bool
    - Nested attrs classes (ForeignKey relationships)
    - list[AttrsClass] (Many-to-Many and reverse FK relationships)
    - Optional types: Optional[T] and T | None
    - Custom pairs via @overrides decorator
    - Complete custom spec via @spec decorator
    """
    if not attrs.has(attrs_cls):
        raise TypeError("Spec generation requires an attrs class")

    # Check for custom spec first (short-circuits all other logic)
    if hasattr(attrs_cls, "__mantle_spec__"):
        return attrs_cls.__mantle_spec__

    basic_types = {
        int,
        float,
        Decimal,
        str,
        bool,
    }

    # Get overrides if they exist
    overrides_dict = getattr(attrs_cls, "__mantle_overrides__", {})

    spec = []
    for attribute in attrs.fields(attrs_cls):
        # Check for override first
        if attribute.name in overrides_dict:
            pair = overrides_dict[attribute.name]
            spec.append({attribute.name: pair})
            continue

        attribute_type = attribute.type

        # Handle Optional types (Optional[T] or T | None)
        # typing.get_origin returns Union for Optional[T]
        # In Python 3.10+, T | None creates types.UnionType instead
        origin = typing.get_origin(attribute_type)
        if origin in UNION_TYPES:
            # Get the union args (e.g., [str, None] for Optional[str])
            args = typing.get_args(attribute_type)
            # Filter out NoneType to get the actual type
            non_none_types = [t for t in args if t is not type(None)]

            if len(non_none_types) == 1:
                # This is Optional[T] or T | None - unwrap it
                attribute_type = non_none_types[0]
            elif len(non_none_types) == 0:
                # This is just None, which is unusual
                raise NotImplementedError(
                    f"Attribute {attribute.name} has type None (no non-None types in Union)"
                )
            else:
                # This is a Union with multiple non-None types (e.g., str | int)
                # We don't support this yet
                raise NotImplementedError(
                    f"Attribute {attribute.name} has Union type with multiple non-None types: {attribute_type}"
                )

        if attribute_type in basic_types:
            spec.append(attribute.name)
            continue

        # Handle list[AttrsClass] (M2M and reverse FK)
        if typing.get_origin(attribute_type) == list:
            inner_type = typing.get_args(attribute_type)[0]
            if attrs.has(inner_type):
                spec.append({attribute.name: to_spec(inner_type)})
                continue

        # Handle nested attrs classes (ForeignKey)
        if attrs.has(attribute_type):
            spec.append({attribute.name: to_spec(attribute_type)})
            continue

        # Otherwise raise.
        raise NotImplementedError(
            f"Unhandled attribute type: {attribute.name}: {attribute_type}"
        )

    return spec
