"""PostgreSQL implementation of DeckRepository.

This module provides the PostgreSQL-specific implementation of the
DeckRepository protocol using SQLAlchemy async sessions.

Example:
    async with get_db_session() as db:
        repo = PostgresDeckRepository(db)
        decks = await repo.get_by_user(user_id)
"""

from uuid import UUID

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.db.models.deck import Deck
from app.repositories.protocols import UNSET, DeckEntry


def _to_dto(model: Deck) -> DeckEntry:
    """Convert ORM model to DTO."""
    return DeckEntry(
        id=model.id,
        user_id=model.user_id,
        name=model.name,
        cards=model.cards or {},
        energy_cards=model.energy_cards or {},
        is_valid=model.is_valid,
        validation_errors=model.validation_errors,
        is_starter=model.is_starter,
        starter_type=model.starter_type,
        description=model.description,
        created_at=model.created_at,
        updated_at=model.updated_at,
    )


class PostgresDeckRepository:
    """PostgreSQL implementation of DeckRepository.

    Uses SQLAlchemy async sessions for database access.

    Attributes:
        _db: The async database session.
    """

    def __init__(self, db: AsyncSession) -> None:
        """Initialize with database session.

        Args:
            db: SQLAlchemy async session.
        """
        self._db = db

    async def get_by_id(self, deck_id: UUID) -> DeckEntry | None:
        """Get a deck by its ID.

        Args:
            deck_id: The deck's UUID.

        Returns:
            DeckEntry if found, None otherwise.
        """
        result = await self._db.execute(select(Deck).where(Deck.id == deck_id))
        model = result.scalar_one_or_none()
        return _to_dto(model) if model else None

    async def get_by_user(self, user_id: UUID) -> list[DeckEntry]:
        """Get all decks for a user.

        Args:
            user_id: The user's UUID.

        Returns:
            List of all user's decks, ordered by name.
        """
        result = await self._db.execute(
            select(Deck).where(Deck.user_id == user_id).order_by(Deck.name)
        )
        return [_to_dto(model) for model in result.scalars().all()]

    async def get_user_deck(self, user_id: UUID, deck_id: UUID) -> DeckEntry | None:
        """Get a specific deck owned by a user.

        Combines ownership check with retrieval.

        Args:
            user_id: The user's UUID.
            deck_id: The deck's UUID.

        Returns:
            DeckEntry if found and owned by user, None otherwise.
        """
        result = await self._db.execute(
            select(Deck).where(
                Deck.id == deck_id,
                Deck.user_id == user_id,
            )
        )
        model = result.scalar_one_or_none()
        return _to_dto(model) if model else None

    async def count_by_user(self, user_id: UUID) -> int:
        """Count how many decks a user has.

        Args:
            user_id: The user's UUID.

        Returns:
            Number of decks owned by user.
        """
        result = await self._db.execute(select(func.count(Deck.id)).where(Deck.user_id == user_id))
        return result.scalar_one()

    async def create(
        self,
        user_id: UUID,
        name: str,
        cards: dict[str, int],
        energy_cards: dict[str, int],
        is_valid: bool,
        validation_errors: list[str] | None,
        is_starter: bool = False,
        starter_type: str | None = None,
        description: str | None = None,
    ) -> DeckEntry:
        """Create a new deck.

        Args:
            user_id: The user's UUID.
            name: Display name for the deck.
            cards: Card ID to quantity mapping.
            energy_cards: Energy type to quantity mapping.
            is_valid: Whether deck passes validation.
            validation_errors: List of validation error messages.
            is_starter: Whether this is a starter deck.
            starter_type: Type of starter deck if applicable.
            description: Optional deck description.

        Returns:
            The created DeckEntry.
        """
        deck = Deck(
            user_id=user_id,
            name=name,
            cards=cards,
            energy_cards=energy_cards,
            is_valid=is_valid,
            validation_errors=validation_errors,
            is_starter=is_starter,
            starter_type=starter_type,
            description=description,
        )
        self._db.add(deck)
        await self._db.commit()
        await self._db.refresh(deck)
        return _to_dto(deck)

    async def update(
        self,
        deck_id: UUID,
        name: str | None = None,
        cards: dict[str, int] | None = None,
        energy_cards: dict[str, int] | None = None,
        is_valid: bool | None = None,
        validation_errors: list[str] | None = UNSET,  # type: ignore[assignment]
        description: str | None = UNSET,  # type: ignore[assignment]
    ) -> DeckEntry | None:
        """Update an existing deck.

        Only provided (non-UNSET) fields are updated.
        Use UNSET (default) to keep existing value, or None to clear.

        Args:
            deck_id: The deck's UUID.
            name: New name (optional, None keeps existing).
            cards: New card composition (optional, None keeps existing).
            energy_cards: New energy composition (optional, None keeps existing).
            is_valid: New validation status (optional, None keeps existing).
            validation_errors: New errors (UNSET=keep, None=clear, list=set).
            description: New description (UNSET=keep, None=clear, str=set).

        Returns:
            Updated DeckEntry, or None if deck not found.
        """
        result = await self._db.execute(select(Deck).where(Deck.id == deck_id))
        deck = result.scalar_one_or_none()

        if deck is None:
            return None

        if name is not None:
            deck.name = name
        if cards is not None:
            deck.cards = cards
        if energy_cards is not None:
            deck.energy_cards = energy_cards
        if is_valid is not None:
            deck.is_valid = is_valid
        # Use UNSET pattern for nullable fields that can be cleared
        if validation_errors is not UNSET:
            deck.validation_errors = validation_errors
        if description is not UNSET:
            deck.description = description

        await self._db.commit()
        await self._db.refresh(deck)
        return _to_dto(deck)

    async def delete(self, deck_id: UUID) -> bool:
        """Delete a deck.

        Args:
            deck_id: The deck's UUID.

        Returns:
            True if deleted, False if not found.
        """
        result = await self._db.execute(select(Deck).where(Deck.id == deck_id))
        deck = result.scalar_one_or_none()

        if deck is None:
            return False

        await self._db.delete(deck)
        await self._db.commit()
        return True

    async def has_starter(self, user_id: UUID) -> tuple[bool, str | None]:
        """Check if user has a starter deck.

        Args:
            user_id: The user's UUID.

        Returns:
            Tuple of (has_starter, starter_type).
        """
        result = await self._db.execute(
            select(Deck.starter_type)
            .where(
                Deck.user_id == user_id,
                Deck.is_starter == True,  # noqa: E712
            )
            .limit(1)
        )
        starter_type = result.scalar_one_or_none()
        return (starter_type is not None, starter_type)
