"""Deck manager service.""" from typing import List, Dict, Any, Optional from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, update, delete from sqlalchemy.orm import selectinload from app.models.user_deck import UserDeck, UserDeckCard, DeckPrecedent, DeckPrecedentCard from app.models.models import MtgonlineCard class DeckManager: """Deck manager service.""" @staticmethod async def create_deck( db: AsyncSession, user_id: int, name: str, folder_id: Optional[int] = None, format: str = "standard", notes: Optional[str] = None, is_precedent: bool = False, precedent_name: Optional[str] = None, ) -> UserDeck: """Create a new deck.""" deck = UserDeck( user_id=user_id, name=name, folder_id=folder_id, format=format, notes=notes, is_precedent=is_precedent, precedent_name=precedent_name, ) db.add(deck) await db.flush() return deck @staticmethod async def get_deck(db: AsyncSession, deck_id: int, user_id: int) -> Optional[UserDeck]: """Get a deck by ID.""" stmt = select(UserDeck).where(UserDeck.id == deck_id, UserDeck.user_id == user_id) result = await db.execute(stmt) return result.scalar_one_or_none() @staticmethod async def list_decks( db: AsyncSession, user_id: int, status_filter: Optional[str] = None, folder_id: Optional[int] = None, is_precedent: Optional[bool] = None, page: int = 1, page_size: int = 50, ) -> List[UserDeck]: """List user's decks with filtering.""" conditions = [UserDeck.user_id == user_id] if status_filter: conditions.append(UserDeck.status == status_filter) if folder_id: conditions.append(UserDeck.folder_id == folder_id) if is_precedent is not None: conditions.append(UserDeck.is_precedent == is_precedent) offset = (page - 1) * page_size stmt = select(UserDeck).where(*conditions).order_by(UserDeck.updated_at.desc()).offset(offset).limit(page_size) result = await db.execute(stmt) return result.scalars().all() @staticmethod async def update_deck( db: AsyncSession, deck_id: int, user_id: int, name: Optional[str] = None, folder_id: Optional[int] = None, format: Optional[str] = None, notes: Optional[str] = None, ) -> Optional[UserDeck]: """Update a deck.""" deck = await DeckManager.get_deck(db, deck_id, user_id) if not deck: return None if deck.status == "FINAL": raise ValueError("Cannot modify a finalized deck") if name: deck.name = name if folder_id is not None: deck.folder_id = folder_id if format: deck.format = format if notes is not None: deck.notes = notes await db.flush() return deck @staticmethod async def delete_deck(db: AsyncSession, deck_id: int, user_id: int) -> bool: """Delete a deck.""" deck = await DeckManager.get_deck(db, deck_id, user_id) if not deck: return False await db.execute(delete(UserDeck).where(UserDeck.id == deck_id)) await db.flush() return True @staticmethod async def finalize_deck(db: AsyncSession, deck_id: int, user_id: int) -> Optional[UserDeck]: """Transition a deck from DRAFT to FINAL status.""" deck = await DeckManager.get_deck(db, deck_id, user_id) if not deck: return None if deck.status == "FINAL": raise ValueError("Deck is already finalized") # Check deck has cards card_count_stmt = select(func.count()).select_from(UserDeckCard).where(UserDeckCard.deck_id == deck_id) card_count_result = await db.execute(card_count_stmt) card_count = card_count_result.scalar() or 0 if card_count == 0: raise ValueError("Cannot finalize an empty deck") deck.status = "FINAL" await db.flush() return deck @staticmethod async def add_card_to_deck( db: AsyncSession, deck_id: int, card_id: int, quantity: int = 1, zone: str = "main", position: Optional[int] = None, ) -> UserDeckCard: """Add a card to a deck.""" deck_card = UserDeckCard( deck_id=deck_id, card_id=card_id, quantity=quantity, zone=zone, position=position, ) db.add(deck_card) await db.flush() return deck_card @staticmethod async def get_deck_cards(db: AsyncSession, deck_id: int, zone: Optional[str] = None) -> List[UserDeckCard]: """Get cards in a deck.""" conditions = [UserDeckCard.deck_id == deck_id] if zone: conditions.append(UserDeckCard.zone == zone) stmt = select(UserDeckCard).where(*conditions).order_by(UserDeckCard.id) result = await db.execute(stmt) return result.scalars().all() @staticmethod async def update_deck_card( db: AsyncSession, deck_card_id: int, quantity: Optional[int] = None, zone: Optional[str] = None, position: Optional[int] = None, ) -> Optional[UserDeckCard]: """Update a card in a deck.""" stmt = select(UserDeckCard).where(UserDeckCard.id == deck_card_id) result = await db.execute(stmt) deck_card = result.scalar_one_or_none() if not deck_card: return None if quantity is not None: deck_card.quantity = quantity if zone: deck_card.zone = zone if position is not None: deck_card.position = position await db.flush() return deck_card @staticmethod async def remove_card_from_deck(db: AsyncSession, deck_card_id: int) -> bool: """Remove a card from a deck.""" stmt = select(UserDeckCard).where(UserDeckCard.id == deck_card_id) result = await db.execute(stmt) deck_card = result.scalar_one_or_none() if not deck_card: return False await db.execute(delete(UserDeckCard).where(UserDeckCard.id == deck_card_id)) await db.flush() return True @staticmethod async def clone_precedent( db: AsyncSession, precedent_id: int, user_id: int, name: Optional[str] = None, ) -> UserDeck: """Clone a precedent into a new deck.""" # Get precedent stmt = select(DeckPrecedent).where(DeckPrecedent.id == precedent_id) result = await db.execute(stmt) precedent = result.scalar_one_or_none() if not precedent: raise ValueError(f"Precedent {precedent_id} not found") # Create new deck new_name = name or f"Copy of {precedent.name}" new_deck = UserDeck( user_id=user_id, name=new_name, format=precedent.format, is_precedent=False, ) db.add(new_deck) await db.flush() # Copy cards from precedent card_stmt = select(DeckPrecedentCard).where(DeckPrecedentCard.precedent_id == precedent_id) card_result = await db.execute(card_stmt) precedent_cards = card_result.scalars().all() for pc in precedent_cards: new_dc = UserDeckCard( deck_id=new_deck.id, card_id=pc.card_id, quantity=pc.quantity, zone=pc.zone, ) db.add(new_dc) await db.flush() return new_deck