""" MTG Card Database Service. Queries the MTG PostgreSQL database for card data. """ from typing import List, Dict, Any, Optional from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_, or_, func from sqlalchemy.orm import selectinload from app.models.mtg_models import MtgCard, MtgSet from app.core.database import mtg_get_db async def search_cards( query: str, db: AsyncSession, limit: int = 100, offset: int = 0, ) -> Dict[str, Any]: """ Search cards by name, type, or mana cost. Args: query: Search string db: Database session limit: Maximum results to return offset: Number of results to skip Returns: Dictionary with results and total count """ search_term = f"%{query.lower()}%" # Search across multiple fields stmt = ( select(MtgCard, MtgSet) .join(MtgSet, MtgCard.set_id == MtgSet.id, isouter=True) .where( or_( MtgCard.name.ilike(search_term), MtgCard.type_line.ilike(search_term), MtgCard.mana_cost.ilike(search_term), ) ) .offset(offset) .limit(limit) ) result = await db.execute(stmt) rows = result.all() cards = [] for card, mtg_set in rows: card_data = { "id": card.id, "name": card.name, "mana_cost": card.mana_cost, "type_line": card.type_line, "oracle_text": card.oracle_text, "power": card.power, "toughness": card.toughness, "rarity": card.rarity, "layout": card.layout, "artist": card.artist, "flavor_text": card.flavor_text, "set_code": mtg_set.code if mtg_set else None, "set_name": mtg_set.name if mtg_set else None, "release_date": mtg_set.release_date.isoformat() if mtg_set and mtg_set.release_date else None, "identifiers": card.identifiers, "images": card.images, } cards.append(card_data) # Get total count count_stmt = select(func.count()).select_from(MtgCard) count_result = await db.execute(count_stmt) total = count_result.scalar() return { "results": cards, "total": total, "limit": limit, "offset": offset, } async def get_card_by_name( name: str, db: AsyncSession, set_code: Optional[str] = None, ) -> Optional[Dict[str, Any]]: """ Get a specific card by name. Args: name: Card name db: Database session set_code: Optional set code to filter by Returns: Card data or None """ stmt = ( select(MtgCard, MtgSet) .join(MtgSet, MtgCard.set_id == MtgSet.id, isouter=True) .where(MtgCard.name.ilike(name)) ) if set_code: stmt = stmt.where(MtgSet.code == set_code) stmt = stmt.limit(1) result = await db.execute(stmt) row = result.fetchone() if not row: return None card, mtg_set = row return { "id": card.id, "name": card.name, "mana_cost": card.mana_cost, "type_line": card.type_line, "oracle_text": card.oracle_text, "power": card.power, "toughness": card.toughness, "rarity": card.rarity, "layout": card.layout, "artist": card.artist, "flavor_text": card.flavor_text, "set_code": mtg_set.code if mtg_set else None, "set_name": mtg_set.name if mtg_set else None, "release_date": mtg_set.release_date.isoformat() if mtg_set and mtg_set.release_date else None, "identifiers": card.identifiers, "images": card.images, } async def get_cards_by_set( set_code: str, db: AsyncSession, limit: int = 1000, offset: int = 0, ) -> Dict[str, Any]: """ Get all cards in a specific set. Args: set_code: Set code db: Database session limit: Maximum results to return offset: Number of results to skip Returns: Dictionary with results and total count """ # First get the set set_stmt = select(MtgSet).where(MtgSet.code == set_code) set_result = await db.execute(set_stmt) mtg_set = set_result.scalar_one_or_none() if not mtg_set: return {"results": [], "total": 0, "limit": limit, "offset": offset} # Get cards in the set stmt = ( select(MtgCard, MtgSet) .join(MtgSet, MtgCard.set_id == MtgSet.id, isouter=True) .where(MtgCard.set_id == mtg_set.id) .offset(offset) .limit(limit) ) result = await db.execute(stmt) rows = result.all() cards = [] for card, _ in rows: card_data = { "id": card.id, "name": card.name, "mana_cost": card.mana_cost, "type_line": card.type_line, "oracle_text": card.oracle_text, "power": card.power, "toughness": card.toughness, "rarity": card.rarity, "layout": card.layout, "artist": card.artist, "flavor_text": card.flavor_text, "set_code": mtg_set.code, "set_name": mtg_set.name, "release_date": mtg_set.release_date.isoformat() if mtg_set.release_date else None, "identifiers": card.identifiers, "images": card.images, } cards.append(card_data) # Get total count count_stmt = select(func.count()).where(MtgCard.set_id == mtg_set.id) count_result = await db.execute(count_stmt) total = count_result.scalar() return { "results": cards, "total": total, "limit": limit, "offset": offset, } async def get_card_types(db: AsyncSession) -> List[Dict[str, Any]]: """ Get all unique card types. Args: db: Database session Returns: List of card types """ stmt = select(MtgCard.type_line).distinct().order_by(MtgCard.type_line) result = await db.execute(stmt) rows = result.fetchall() return [{"type": row[0]} for row in rows] async def get_card_rarities(db: AsyncSession) -> List[Dict[str, Any]]: """ Get all unique card rarities. Args: db: Database session Returns: List of rarities """ stmt = select(MtgCard.rarity).distinct().order_by(MtgCard.rarity) result = await db.execute(stmt) rows = result.fetchall() return [{"rarity": row[0]} for row in rows] async def get_sets(db: AsyncSession) -> List[Dict[str, Any]]: """ Get all sets. Args: db: Database session Returns: List of sets """ stmt = select(MtgSet).order_by(MtgSet.release_date.desc()) result = await db.execute(stmt) rows = result.fetchall() return [ { "id": s.id, "code": s.code, "name": s.name, "release_date": s.release_date.isoformat() if s.release_date else None, "total_size": s.total_size, "base_set_size": s.base_set_size, } for s in rows ] async def get_set_by_code(code: str, db: AsyncSession) -> Optional[Dict[str, Any]]: """ Get a specific set by code. Args: code: Set code db: Database session Returns: Set data or None """ stmt = select(MtgSet).where(MtgSet.code == code) result = await db.execute(stmt) mtg_set = result.scalar_one_or_none() if not mtg_set: return None return { "id": mtg_set.id, "code": mtg_set.code, "name": mtg_set.name, "type": mtg_set.type, "release_date": mtg_set.release_date.isoformat() if mtg_set.release_date else None, "base_set_size": mtg_set.base_set_size, "total_size": mtg_set.total_size, "is_foil_only": mtg_set.is_foil_only, "is_non_foil_only": mtg_set.is_non_foil_only, "digital": mtg_set.digital, "icon_svg_url": mtg_set.icon_svg_url, "parent_code": mtg_set.parent_code, "mtgo_code": mtg_set.mtgo_code, } async def get_card_statistics(db: AsyncSession) -> Dict[str, Any]: """ Get overall card database statistics. Args: db: Database session Returns: Dictionary with statistics """ # Total cards card_count_stmt = select(func.count()).select_from(MtgCard) card_count = (await db.execute(card_count_stmt)).scalar() # Total sets set_count_stmt = select(func.count()).select_from(MtgSet) set_count = (await db.execute(set_count_stmt)).scalar() # Cards by rarity rarity_stmt = select(MtgCard.rarity, func.count()).group_by(MtgCard.rarity) rarity_result = await db.execute(rarity_stmt) rarities = {row[0]: row[1] for row in rarity_result} # Cards by type type_stmt = select(MtgCard.type_line, func.count()).group_by(MtgCard.type_line) type_result = await db.execute(type_stmt) types = {row[0]: row[1] for row in type_result} # Average mana cost (approximate) avg_mana_stmt = select(func.count()).where(MtgCard.mana_cost.isnot(None)) avg_mana_count = (await db.execute(avg_mana_stmt)).scalar() return { "total_cards": card_count, "total_sets": set_count, "cards_by_rarity": rarities, "cards_by_type": types, "cards_with_mana_cost": avg_mana_count, }