"""Deck validation functions for Mantimon TCG.

This module provides pure validation functions that validate deck compositions
against rules provided by the caller. The backend is stateless - rules come
from the request via DeckConfig.

Usage:
    from app.core.config import DeckConfig
    from app.services.deck_validator import validate_deck, ValidationResult

    result = validate_deck(
        cards={"a1-001-bulbasaur": 4, ...},
        energy_cards={"grass": 14, "colorless": 6},
        deck_config=DeckConfig(),  # Or custom rules from request
        card_lookup=card_service.get_card,
        owned_cards=user_collection,  # None to skip ownership check
    )

    if not result.is_valid:
        for error in result.errors:
            print(error)
"""

from collections.abc import Callable
from dataclasses import dataclass, field

from app.core.config import DeckConfig
from app.core.enums import EnergyType
from app.core.models.card import CardDefinition

# Set of valid energy type names (lowercase values from EnergyType enum)
VALID_ENERGY_TYPES: frozenset[str] = frozenset(e.value for e in EnergyType)


@dataclass
class ValidationResult:
    """Result of deck validation.

    Contains validation status and all errors found. Multiple errors
    can be returned to help the user fix all issues at once.
    """

    is_valid: bool = True
    errors: list[str] = field(default_factory=list)

    def add_error(self, error: str) -> None:
        """Add an error and mark as invalid."""
        self.is_valid = False
        self.errors.append(error)


def validate_deck(
    cards: dict[str, int],
    energy_cards: dict[str, int],
    deck_config: DeckConfig,
    card_lookup: Callable[[str], CardDefinition | None],
    owned_cards: dict[str, int] | None = None,
) -> ValidationResult:
    """Validate a deck composition against provided rules.

    This is a pure function - all inputs are provided by the caller,
    including the rules to validate against via DeckConfig.

    Args:
        cards: Mapping of card IDs to quantities for the main deck.
        energy_cards: Mapping of energy type names to quantities.
        deck_config: Deck rules from the caller (DeckConfig from app.core.config).
        card_lookup: Function to look up card definitions by ID.
        owned_cards: If provided, validates that the user owns enough
            copies of each card. Pass None to skip ownership validation.

    Returns:
        ValidationResult with is_valid status and list of errors.

    Example:
        result = validate_deck(
            cards={"a1-001-bulbasaur": 4},
            energy_cards={"grass": 20},
            deck_config=DeckConfig(min_size=40, energy_deck_size=20),
            card_lookup=card_service.get_card,
        )
    """
    result = ValidationResult()

    # 1. Validate total card count
    total_cards = sum(cards.values())
    if total_cards != deck_config.min_size:
        result.add_error(
            f"Main deck must have exactly {deck_config.min_size} cards, got {total_cards}"
        )

    # 2. Validate total energy count
    total_energy = sum(energy_cards.values())
    if total_energy != deck_config.energy_deck_size:
        result.add_error(
            f"Energy deck must have exactly {deck_config.energy_deck_size} cards, "
            f"got {total_energy}"
        )

    # 2b. Validate energy types are valid
    invalid_energy_types = [et for et in energy_cards if et not in VALID_ENERGY_TYPES]
    if invalid_energy_types:
        display_types = invalid_energy_types[:5]
        more = len(invalid_energy_types) - 5
        error_msg = f"Invalid energy types: {', '.join(display_types)}"
        if more > 0:
            error_msg += f" (and {more} more)"
        result.add_error(error_msg)

    # 3. Validate max copies per card
    for card_id, quantity in cards.items():
        if quantity > deck_config.max_copies_per_card:
            result.add_error(
                f"Card '{card_id}' has {quantity} copies, "
                f"max allowed is {deck_config.max_copies_per_card}"
            )

    # 4 & 5. Validate card IDs exist and count Basic Pokemon
    basic_pokemon_count = 0
    invalid_card_ids: list[str] = []

    for card_id in cards:
        card_def = card_lookup(card_id)
        if card_def is None:
            invalid_card_ids.append(card_id)
        elif card_def.is_basic_pokemon():
            basic_pokemon_count += cards[card_id]

    if invalid_card_ids:
        display_ids = invalid_card_ids[:5]
        more = len(invalid_card_ids) - 5
        error_msg = f"Invalid card IDs: {', '.join(display_ids)}"
        if more > 0:
            error_msg += f" (and {more} more)"
        result.add_error(error_msg)

    # Check minimum Basic Pokemon requirement
    if basic_pokemon_count < deck_config.min_basic_pokemon:
        result.add_error(
            f"Deck must have at least {deck_config.min_basic_pokemon} Basic Pokemon, "
            f"got {basic_pokemon_count}"
        )

    # 6. Validate ownership if owned_cards provided
    if owned_cards is not None:
        insufficient_cards: list[tuple[str, int, int]] = []
        for card_id, required_qty in cards.items():
            owned_qty = owned_cards.get(card_id, 0)
            if owned_qty < required_qty:
                insufficient_cards.append((card_id, required_qty, owned_qty))

        if insufficient_cards:
            display_cards = insufficient_cards[:5]
            more = len(insufficient_cards) - 5
            error_parts = [f"'{c[0]}' (need {c[1]}, own {c[2]})" for c in display_cards]
            error_msg = f"Insufficient cards: {', '.join(error_parts)}"
            if more > 0:
                error_msg += f" (and {more} more)"
            result.add_error(error_msg)

    return result


def validate_cards_exist(
    card_ids: list[str],
    card_lookup: Callable[[str], CardDefinition | None],
) -> list[str]:
    """Check which card IDs are invalid.

    Args:
        card_ids: List of card IDs to check.
        card_lookup: Function to look up card definitions.

    Returns:
        List of invalid card IDs (empty if all valid).
    """
    return [card_id for card_id in card_ids if card_lookup(card_id) is None]


def count_basic_pokemon(
    cards: dict[str, int],
    card_lookup: Callable[[str], CardDefinition | None],
) -> int:
    """Count Basic Pokemon in a deck.

    Args:
        cards: Mapping of card IDs to quantities.
        card_lookup: Function to look up card definitions.

    Returns:
        Total number of Basic Pokemon cards in the deck.
    """
    count = 0
    for card_id, quantity in cards.items():
        card_def = card_lookup(card_id)
        if card_def and card_def.is_basic_pokemon():
            count += quantity
    return count


def validate_energy_types(energy_types: list[str]) -> list[str]:
    """Check which energy type names are invalid.

    Args:
        energy_types: List of energy type names to check.

    Returns:
        List of invalid energy type names (empty if all valid).

    Example:
        invalid = validate_energy_types(["grass", "fire", "invalid"])
        # Returns: ["invalid"]
    """
    return [et for et in energy_types if et not in VALID_ENERGY_TYPES]
