from enum import Enum
from typing import Any


class Operation(str, Enum):
    """Comparison operations for filtering items."""

    EQUAL = "=="
    """Exact equality comparison."""
    NOT_EQUAL = "!="
    """Not equal comparison."""
    CONTAIN = "in"
    """Check if value is contained in the field (substring match)."""
    NOT_CONTAIN = "not_in"
    """Check if value is not contained in the field."""
    GREATER_THAN = ">"
    """Greater than comparison."""
    LESS_THAN = "<"
    """Less than comparison."""
    GREATER_EQUAL = ">="
    """Greater than or equal comparison."""
    LESS_EQUAL = "<="
    """Less than or equal comparison."""
    STARTS_WITH = "startswith"
    """Check if field starts with value."""
    ENDS_WITH = "endswith"
    """Check if field ends with value."""

    def __str__(self) -> str:
        return self.value


def matches(operation: Operation | str, haystack: Any, needle: Any) -> bool:
    """
    Generic comparison function that compares two values using the specified operation.

    Args:
        operation: The comparison operation to perform (Operation enum or string)
        haystack: The first value (usually the field value from data)
        needle: The second value (usually the comparison value)

    Returns:
        bool: True if the comparison matches, False otherwise

    Examples:
        >>> matches(Operation.EQUAL, "test", "test")
        True
        >>> matches(Operation.CONTAIN, "Python Tutorial", "Python")
        True
        >>> matches(Operation.GREATER_THAN, 100, 50)
        True
        >>> matches("==", "test", "test")
        True

    """
    # Parse operation if it's a string
    if isinstance(operation, str):
        try:
            operation = Operation(operation)
        except ValueError:
            operation = Operation.EQUAL

    try:
        if Operation.EQUAL == operation:
            return haystack == needle

        if Operation.NOT_EQUAL == operation:
            return haystack != needle

        if Operation.CONTAIN == operation:
            return str(needle) in str(haystack) if haystack is not None else False

        if Operation.NOT_CONTAIN == operation:
            return str(needle) not in str(haystack) if haystack is not None else True

        if Operation.GREATER_THAN == operation:
            if haystack is None or needle is None:
                return False
            return haystack > needle

        if Operation.LESS_THAN == operation:
            if haystack is None or needle is None:
                return False
            return haystack < needle

        if Operation.GREATER_EQUAL == operation:
            if haystack is None or needle is None:
                return False
            return haystack >= needle

        if Operation.LESS_EQUAL == operation:
            if haystack is None or needle is None:
                return False
            return haystack <= needle

        if Operation.STARTS_WITH == operation:
            return str(haystack).startswith(str(needle)) if haystack is not None else False

        if Operation.ENDS_WITH == operation:
            return str(haystack).endswith(str(needle)) if haystack is not None else False

        # Unknown operation, default to equality
        return haystack == needle

    except (TypeError, AttributeError):
        # Comparison failed (e.g., comparing incompatible types)
        return False


def matches_condition(key: str, value: tuple | str | float | bool, data: dict) -> bool:
    """
    Check if a field in a dictionary matches the given condition.

    This is a helper function that extracts values from a dictionary and uses the generic
    matches() function to perform the comparison.

    Args:
        key: The field name to check in the data dictionary
        value: Either:
            - A direct value for equality check: "test"
            - A tuple of (Operation, value): (Operation.CONTAIN, "test")
            - A tuple of (str, value): ("in", "test") for backward compatibility
        data: Dictionary containing the data to check against

    Returns:
        bool: True if the condition matches, False otherwise

    Examples:
        >>> data = {"title": "Python Tutorial", "size": 1000}
        >>> matches_condition("title", "Python Tutorial", data)
        True
        >>> matches_condition("title", (Operation.CONTAIN, "Python"), data)
        True
        >>> matches_condition("size", (Operation.GREATER_THAN, 500), data)
        True
        >>> matches_condition("missing", "value", data)
        False

    """
    if key not in data:
        return False

    field_value: Any = data[key]

    # Parse value to extract operation and comparison value
    if isinstance(value, tuple) and len(value) == 2:
        operation, compare_value = value
    else:
        operation = Operation.EQUAL
        compare_value = value

    return matches(operation, field_value, compare_value)


def matches_all(data: dict, **conditions) -> bool:
    """
    Check if all conditions match (AND logic).

    Args:
        data: Dictionary containing the data to check against
        **conditions: Keyword arguments representing conditions to check

    Returns:
        bool: True if all conditions match, False otherwise

    Examples:
        >>> data = {"title": "Python Tutorial", "size": 1000, "status": "active"}
        >>> matches_all(data, title=(Operation.CONTAIN, "Python"), size=(Operation.GREATER_THAN, 500))
        True
        >>> matches_all(data, title="Python Tutorial", status="active")
        True

    """
    if not conditions:
        return True

    return all(matches_condition(key, value, data) for key, value in conditions.items())


def matches_any(data: dict, **conditions) -> bool:
    """
    Check if any condition matches (OR logic).

    Args:
        data: Dictionary containing the data to check against
        **conditions: Keyword arguments representing conditions to check

    Returns:
        bool: True if any condition matches, False if none match

    Examples:
        >>> data = {"title": "Python Tutorial", "size": 1000}
        >>> matches_any(data, title=(Operation.CONTAIN, "Java"), size=(Operation.GREATER_THAN, 500))
        True
        >>> matches_any(data, title="Wrong", status="Wrong")
        False

    """
    if not conditions:
        return False

    return any(matches_condition(key, value, data) for key, value in conditions.items())


def filter_items(items: list[dict], **conditions) -> list[dict]:
    """
    Filter a list of dictionaries based on conditions (AND logic).

    Args:
        items: List of dictionaries to filter
        **conditions: Keyword arguments representing conditions to check

    Returns:
        list[dict]: Filtered list of dictionaries that match all conditions

    Examples:
        >>> items = [
        ...     {"title": "Python Tutorial", "size": 1000},
        ...     {"title": "JavaScript Course", "size": 2000},
        ...     {"title": "Python Advanced", "size": 1500}
        ... ]
        >>> filter_items(items, title=(Operation.CONTAIN, "Python"))
        [{"title": "Python Tutorial", "size": 1000}, {"title": "Python Advanced", "size": 1500}]
        >>> filter_items(items, size=(Operation.GREATER_THAN, 1200))
        [{"title": "JavaScript Course", "size": 2000}, {"title": "Python Advanced", "size": 1500}]

    """
    if not conditions:
        return items

    return [item for item in items if matches_all(item, **conditions)]


def find_first(items: list[dict], **conditions) -> dict | None:
    """
    Find the first dictionary that matches all conditions (AND logic).

    Args:
        items: List of dictionaries to search
        **conditions: Keyword arguments representing conditions to check

    Returns:
        dict | None: First matching dictionary or None if no match found

    Examples:
        >>> items = [
        ...     {"title": "Python Tutorial", "size": 1000},
        ...     {"title": "JavaScript Course", "size": 2000}
        ... ]
        >>> find_first(items, title=(Operation.CONTAIN, "Python"))
        {"title": "Python Tutorial", "size": 1000}
        >>> find_first(items, title="Nonexistent")
        None

    """
    for item in items:
        if matches_all(item, **conditions):
            return item
    return None


def find_all(items: list[dict], **conditions) -> list[dict]:
    """
    Alias for filter_items() - find all dictionaries matching conditions.

    Args:
        items: List of dictionaries to search
        **conditions: Keyword arguments representing conditions to check

    Returns:
        list[dict]: List of matching dictionaries

    """
    return filter_items(items, **conditions)
