"""Collection service for Mantimon TCG.

This module provides business logic for user card collections. It uses
the CollectionRepository protocol for data access, enabling easy testing
and multiple storage backends (PostgreSQL, SQLite, local files).

The service layer handles:
- Card ID validation via CardService
- Starter deck granting logic
- Collection statistics

Example:
    from app.services.card_service import CardService
    from app.services.collection_service import CollectionService
    from app.repositories.postgres import PostgresCollectionRepository

    # Create dependencies
    card_service = CardService()
    card_service.load_all()

    # Create repository and service
    repo = PostgresCollectionRepository(db_session)
    service = CollectionService(repo, card_service)

    # Add cards to collection
    entry = await service.add_cards(
        user_id, "a1-001-bulbasaur", quantity=2, source=CardSource.BOOSTER
    )
"""

from uuid import UUID

from app.db.models.collection import CardSource
from app.repositories.protocols import CollectionEntry, CollectionRepository
from app.services.card_service import CardService


class CollectionService:
    """Service for card collection business logic.

    Uses repository pattern for data access, enabling:
    - Easy unit testing with mock repositories
    - Multiple storage backends
    - Offline fork support

    Attributes:
        _repo: The collection repository implementation.
        _card_service: The card service for card validation.
    """

    def __init__(self, repository: CollectionRepository, card_service: CardService) -> None:
        """Initialize with dependencies.

        Args:
            repository: Implementation of CollectionRepository protocol.
            card_service: Card service for validating card IDs.
        """
        self._repo = repository
        self._card_service = card_service

    async def get_collection(self, user_id: UUID) -> list[CollectionEntry]:
        """Get all cards in a user's collection.

        Args:
            user_id: The user's UUID.

        Returns:
            List of CollectionEntry for the user.

        Example:
            collection = await service.get_collection(user_id)
            for entry in collection:
                print(f"{entry.card_definition_id}: {entry.quantity}")
        """
        return await self._repo.get_all(user_id)

    async def get_card_quantity(self, user_id: UUID, card_definition_id: str) -> int:
        """Get quantity of a specific card owned by user.

        Args:
            user_id: The user's UUID.
            card_definition_id: Card ID to check.

        Returns:
            Number of copies owned (0 if not owned).
        """
        return await self._repo.get_quantity(user_id, card_definition_id)

    async def get_collection_entry(
        self, user_id: UUID, card_definition_id: str
    ) -> CollectionEntry | None:
        """Get a specific collection entry.

        Args:
            user_id: The user's UUID.
            card_definition_id: Card ID to look up.

        Returns:
            CollectionEntry if exists, None otherwise.
        """
        return await self._repo.get_by_card(user_id, card_definition_id)

    async def add_cards(
        self,
        user_id: UUID,
        card_definition_id: str,
        quantity: int,
        source: CardSource,
    ) -> CollectionEntry:
        """Add cards to a user's collection.

        Validates that the card ID exists before adding. Uses upsert
        pattern: creates new entry if card not owned, or increments
        quantity if already owned.

        Args:
            user_id: The user's UUID.
            card_definition_id: Card ID to add.
            quantity: Number of copies to add.
            source: How the cards were obtained.

        Returns:
            The created or updated CollectionEntry.

        Raises:
            ValueError: If card_definition_id doesn't exist.

        Example:
            entry = await service.add_cards(
                user_id, "a1-001-bulbasaur",
                quantity=2, source=CardSource.BOOSTER
            )
        """
        # Validate card exists
        if self._card_service.get_card(card_definition_id) is None:
            raise ValueError(f"Invalid card ID: {card_definition_id}")

        return await self._repo.upsert(user_id, card_definition_id, quantity, source)

    async def remove_cards(
        self,
        user_id: UUID,
        card_definition_id: str,
        quantity: int,
    ) -> CollectionEntry | None:
        """Remove cards from a user's collection.

        Decrements quantity. If quantity reaches 0, deletes the entry.

        Args:
            user_id: The user's UUID.
            card_definition_id: Card ID to remove.
            quantity: Number of copies to remove.

        Returns:
            Updated CollectionEntry, or None if card not owned
            or all copies were removed.
        """
        return await self._repo.decrement(user_id, card_definition_id, quantity)

    async def has_cards(
        self,
        user_id: UUID,
        card_requirements: dict[str, int],
    ) -> bool:
        """Check if user owns at least the required quantity of each card.

        Args:
            user_id: The user's UUID.
            card_requirements: Mapping of card IDs to required quantities.

        Returns:
            True if user owns enough of all cards, False otherwise.

        Example:
            can_build = await service.has_cards(
                user_id, {"a1-001-bulbasaur": 4, "a1-002-ivysaur": 2}
            )
        """
        for card_id, required_qty in card_requirements.items():
            owned_qty = await self._repo.get_quantity(user_id, card_id)
            if owned_qty < required_qty:
                return False
        return True

    async def get_owned_cards_dict(self, user_id: UUID) -> dict[str, int]:
        """Get user's collection as a card_id -> quantity mapping.

        Useful for deck validation in campaign mode.

        Args:
            user_id: The user's UUID.

        Returns:
            Dictionary mapping card IDs to quantities.

        Example:
            owned = await service.get_owned_cards_dict(user_id)
            # Pass to validate_deck function
            result = validate_deck(cards, energy, deck_config, card_lookup, owned_cards=owned)
        """
        collection = await self._repo.get_all(user_id)
        return {entry.card_definition_id: entry.quantity for entry in collection}

    async def grant_starter_deck(
        self,
        user_id: UUID,
        starter_type: str,
    ) -> list[CollectionEntry]:
        """Grant all cards from a starter deck to user's collection.

        .. deprecated::
            Use DeckService.select_and_grant_starter_deck() instead, which
            atomically creates the starter deck AND grants the cards with
            race condition protection.

        Uses CardSource.STARTER for all granted cards.

        Args:
            user_id: The user's UUID.
            starter_type: Type of starter deck (grass, fire, water, etc.).

        Returns:
            List of CollectionEntry created/updated.

        Raises:
            ValueError: If starter_type is invalid.

        Example:
            entries = await service.grant_starter_deck(user_id, "grass")
        """
        # Import here to avoid circular dependency
        from app.data.starter_decks import STARTER_TYPES, get_starter_deck

        if starter_type not in STARTER_TYPES:
            raise ValueError(
                f"Invalid starter type: {starter_type}. "
                f"Must be one of: {', '.join(STARTER_TYPES)}"
            )

        starter_deck = get_starter_deck(starter_type)
        entries: list[CollectionEntry] = []

        # Add all cards from the deck
        for card_id, quantity in starter_deck["cards"].items():
            entry = await self.add_cards(user_id, card_id, quantity, CardSource.STARTER)
            entries.append(entry)

        return entries

    async def get_collection_stats(self, user_id: UUID) -> dict[str, int]:
        """Get aggregate statistics for user's collection.

        Args:
            user_id: The user's UUID.

        Returns:
            Dictionary with total_unique_cards and total_card_count.
        """
        collection = await self._repo.get_all(user_id)
        return {
            "total_unique_cards": len(collection),
            "total_card_count": sum(entry.quantity for entry in collection),
        }
