"""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.models import UserDeck, UserDeckCard, DeckPrecedent, DeckPrecedentCard, 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