"""
Universal filter utility for any dataclass-based object in the Tarot project.

Provides a single, reusable filter mechanism that works across all modules:
- Letters (TarotLetter)
- Cards (Card)
- Numbers (Number)
- Words (Word)
- Any custom dataclass

Usage:
    from utils.filter import universal_filter, get_filterable_fields

    # For TarotLetter
    results = universal_filter(Tarot.letters.all(), letter_type="Mother")

    # For Card
    results = universal_filter(Tarot.deck.cards, arcana="Major")

    # Get available fields for introspection
    fields = get_filterable_fields(TarotLetter)
"""

from dataclasses import fields, is_dataclass
from typing import Any, Dict, List, TypeVar

from utils.object_formatting import (
    format_value,
    get_item_label,
    get_object_attributes,
    is_nested_object,
)

T = TypeVar("T")  # Generic type for any dataclass


def get_filterable_fields(dataclass_type) -> List[str]:
    """
    Dynamically get all filterable fields from a dataclass.

    Args:
        dataclass_type: A dataclass type (e.g., TarotLetter, Card)

    Returns:
        List of field names available for filtering

    Raises:
        TypeError: If the type is not a dataclass

    Example:
        fields = get_filterable_fields(TarotLetter)
        # ['hebrew_letter', 'transliteration', 'letter_type', ...]
    """
    if not is_dataclass(dataclass_type):
        raise TypeError(f"{dataclass_type} is not a dataclass")

    return [f.name for f in fields(dataclass_type)]


def _matches_filter(obj: Any, key: str, value: Any) -> bool:
    """
    Check if an object matches a filter criterion.

    Handles:
    - String matching (case-insensitive)
    - Numeric matching (exact)
    - List matching (any item match)
    - Boolean matching
    - None/null matching
    - Nested object attribute matching (e.g., suit="Cups" matches Suit(name="Cups"))
    - Multiple values (comma-separated strings or lists for OR logic)

    Args:
        obj: The object to check
        key: The attribute name
        value: The value to match against (string, int, list, or comma-separated string)

    Returns:
        True if the object matches the filter, False otherwise

    Examples:
        _matches_filter(card, "number", 3)           # Single number
        _matches_filter(card, "number", [3, 5, 6])   # Multiple numbers (OR)
        _matches_filter(card, "number", "3,5,6")     # Comma-separated (OR)
    """
    attr_value = getattr(obj, key, None)

    if attr_value is None:
        return False

    # Parse multiple values (comma-separated string or list)
    values_to_check = []
    if isinstance(value, str) and "," in value:
        # Split comma-separated string and strip whitespace
        values_to_check = [v.strip() for v in value.split(",")]
    elif isinstance(value, (list, tuple)):
        values_to_check = list(value)
    else:
        values_to_check = [value]

    # Check if attribute matches ANY of the provided values (OR logic)
    for check_value in values_to_check:
        # Handle list attributes (like keywords, colors, etc.)
        if isinstance(attr_value, list):
            if any(str(check_value).lower() == str(item).lower() for item in attr_value):
                return True
            continue

        # Handle numeric comparisons
        if isinstance(check_value, int) and isinstance(attr_value, int):
            if attr_value == check_value:
                return True
            continue

        # Handle boolean comparisons
        if isinstance(check_value, bool) and isinstance(attr_value, bool):
            if attr_value == check_value:
                return True
            continue

        # Handle nested object comparison: if attr_value has a 'name' attribute,
        # try matching the value against that (e.g., suit="Cups" vs Suit(name="Cups"))
        if hasattr(attr_value, "name"):
            nested_name = getattr(attr_value, "name", None)
            if nested_name is not None:
                if str(nested_name).lower() == str(check_value).lower():
                    return True
                continue

        # Handle string comparisons
        if str(attr_value).lower() == str(check_value).lower():
            return True

    return False


def universal_filter(items: List[T], **kwargs) -> List[T]:
    """
    Universal filter function that works on any list of dataclass objects.

    Dynamically filters a list of objects by any combination of their attributes.
    Works with any dataclass-based type throughout the project.

    Args:
        items: List of objects to filter (typically all objects from a collection)
        **kwargs: Attribute filters (field_name=value or field_name=[value1, value2])

    Returns:
        Filtered list containing only items matching ALL criteria

    Features:
        - Case-insensitive string matching
        - Numeric exact matching
        - List item matching (checks if value is in list attribute)
        - Multiple filter support (AND logic between fields, OR logic within a field)
        - Multiple values per field (comma-separated string or list) for OR matching
        - Works with any dataclass object
        - Field aliases (e.g., 'type' -> 'arcana' for Cards)

    Examples:
        # Single values
        results = universal_filter(Tarot.deck.cards, arcana="Major")

        # Multiple values (OR logic) - comma-separated
        results = universal_filter(Tarot.deck.cards, number="3,5,6")

        # Multiple values (OR logic) - list
        results = universal_filter(Tarot.deck.cards, number=[3, 5, 6])

        # Combine multiple fields (AND logic)
        results = universal_filter(Tarot.deck.cards, suit="Cups", type="Court")

        # Multiple values in one field + other filters
        results = universal_filter(Tarot.deck.cards, number="3,5,6", suit="Wands")
    """
    # Field aliases for convenience
    field_aliases = {
        # No aliases - use direct property/field names
    }

    results = items

    for key, value in kwargs.items():
        if value is None:
            continue

        # Apply field alias if it exists
        actual_key = field_aliases.get(key, key)

        results = [obj for obj in results if _matches_filter(obj, actual_key, value)]

    return results


def format_results(items: List[Any]) -> str:
    """
    Format a list of objects for user-friendly display.

    Works with any object type and recursively formats nested structures.
    Each object is separated by a blank line.

    Args:
        items: List of objects to format

    Returns:
        Formatted string with proper indentation and hierarchy

    Example:
        results = universal_filter(Tarot.letters.all(), element="Fire")
        print(format_results(results))
    """
    if not items:
        return "(no items)"

    lines = []
    for item in items:
        # Get label for item (handles name, transliteration, or str())
        label = get_item_label(item, fallback=str(item))
        lines.append(f"--- {label} ---")

        # Format all attributes with proper nesting
        for attr_name, attr_value in get_object_attributes(item):
            if is_nested_object(attr_value):
                lines.append(f"  {attr_name}:")
                lines.append(f"    --- {attr_name.replace('_', ' ').title()} ---")
                nested = format_value(attr_value, indent=4)
                lines.append(nested)
            else:
                lines.append(f"  {attr_name}: {attr_value}")

        lines.append("")  # Blank line between items

    return "\n".join(lines)


# Convenience alias
filter_by = universal_filter


def get_filter_autocomplete(dataclass_type) -> Dict[str, List[str]]:
    """
    Get autocomplete suggestions for filtering a dataclass type.

    Returns a dictionary mapping field names to example values found in the dataclass.
    Useful for IDE autocomplete and CLI help.

    Args:
        dataclass_type: A dataclass type (e.g., TarotLetter, Card, Wall)

    Returns:
        Dictionary with field names as keys and list of example values

    Example:
        autocomplete = get_filter_autocomplete(TarotLetter)
        # Returns:
        # {
        #     'letter_type': ['Mother', 'Double', 'Simple'],
        #     'element': ['Air', 'Fire', 'Water'],
        #     'planet': ['Mercury', 'Mars', ...],
        #     ...
        # }
    """
    if not is_dataclass(dataclass_type):
        raise TypeError(f"{dataclass_type} is not a dataclass")

    autocomplete = {}
    field_names = [f.name for f in fields(dataclass_type)]

    for field_name in field_names:
        autocomplete[field_name] = f"<value for {field_name}>"

    return autocomplete


def describe_filter_fields(dataclass_type) -> str:
    """
    Get a human-readable description of all filterable fields.

    Useful for help text and documentation.

    Args:
        dataclass_type: A dataclass type

    Returns:
        Formatted string with field descriptions

    Example:
        print(describe_filter_fields(TarotLetter))
    """
    if not is_dataclass(dataclass_type):
        raise TypeError(f"{dataclass_type} is not a dataclass")

    field_list = get_filterable_fields(dataclass_type)
    lines = [
        f"Filterable fields for {dataclass_type}:",
        "",
    ]

    for field_name in field_list:
        lines.append(f"  • {field_name}")

    return "\n".join(lines)
