"""Import batch processor service.""" import asyncio from typing import List, Dict, Any, Optional from datetime import datetime from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, update, insert, delete from sqlalchemy.sql import func from app.models.card_import_batch import CardImportBatch from app.models.user_card_import_record import UserCardImportRecord from app.models.user_card_collection import UserCardCollection from app.models.models import MtgonlineCard from app.services.fuzzy_card_matcher import FuzzyCardMatcher class ImportBatchProcessor: """Process card import batches.""" @staticmethod async def create_batch( db: AsyncSession, user_id: int, filename: str, file_type: str, file_size: int, card_names: List[str] ) -> CardImportBatch: """Create a new import batch.""" batch = CardImportBatch( user_id=user_id, filename=filename, file_type=file_type, file_size=file_size, status="pending", total_cards=len(card_names), ) db.add(batch) await db.flush() return batch @staticmethod async def process_batch( db: AsyncSession, batch: CardImportBatch, card_names: List[str], threshold: float = FuzzyCardMatcher.MANUAL_REVIEW_THRESHOLD ) -> Dict[str, Any]: """ Process an import batch. Args: db: Database session batch: Import batch to process card_names: List of card names from the file threshold: Minimum similarity threshold for matching Returns: Dictionary with processing results """ # Update status to processing batch.status = "processing" await db.flush() try: # Fetch all cards from database stmt = select(MtgonlineCard) result = await db.execute(stmt) db_cards = result.scalars().all() # Build candidate list candidate_names = [card.name for card in db_cards if card.name] # Perform batch matching match_results = FuzzyCardMatcher.batch_match_with_database( card_names=card_names, db_session=db, mtgonline_card_model=MtgonlineCard, threshold=threshold ) # Count matches matched_count = sum(1 for _, _, matched_name, _, _ in match_results if matched_name) unmatched_count = sum(1 for _, _, matched_name, _, _ in match_results if not matched_name) # Update batch batch.matched_cards = matched_count batch.unmatched_cards = unmatched_count batch.match_results = match_results batch.status = "completed" batch.updated_at = func.now() await db.flush() return { "batch_id": batch.id, "status": "completed", "total_cards": len(card_names), "matched_cards": matched_count, "unmatched_cards": unmatched_count, "match_results": match_results, } except Exception as e: batch.status = "failed" batch.error_message = str(e) batch.updated_at = func.now() await db.flush() return { "batch_id": batch.id, "status": "failed", "error": str(e), } @staticmethod async def get_batch_status(db: AsyncSession, batch_id: int) -> Optional[CardImportBatch]: """Get the status of an import batch.""" stmt = select(CardImportBatch).where(CardImportBatch.id == batch_id) result = await db.execute(stmt) return result.scalar_one_or_none() @staticmethod async def get_batch_results(db: AsyncSession, batch_id: int) -> Optional[Dict[str, Any]]: """Get the match results for an import batch.""" batch = await ImportBatchProcessor.get_batch_status(db, batch_id) if not batch: return None return batch.match_results @staticmethod async def confirm_batch(db: AsyncSession, batch_id: int, user_id: int) -> UserCardImportRecord: """ Confirm an import batch. Args: db: Database session batch_id: ID of the batch to confirm user_id: ID of the user confirming Returns: UserCardImportRecord for the confirmed import """ batch = await ImportBatchProcessor.get_batch_status(db, batch_id) if not batch: raise ValueError(f"Import batch {batch_id} not found") if batch.status != "completed": raise ValueError(f"Import batch {batch_id} is not completed (status: {batch.status})") # Check if already confirmed stmt = select(UserCardImportRecord).where( UserCardImportRecord.user_id == user_id, UserCardImportRecord.batch_id == batch_id, ) result = await db.execute(stmt) existing = result.scalar_one_or_none() if existing: return existing # Create confirmation record record = UserCardImportRecord( user_id=user_id, batch_id=batch_id, is_confirmed=True, ) db.add(record) await db.flush() return record @staticmethod async def get_user_imports(db: AsyncSession, user_id: int) -> List[CardImportBatch]: """Get all import batches for a user.""" stmt = select(CardImportBatch).where(CardImportBatch.user_id == user_id).order_by(CardImportBatch.created_at.desc()) result = await db.execute(stmt) return result.scalars().all() @staticmethod async def delete_batch(db: AsyncSession, batch_id: int, user_id: int) -> bool: """ Delete an import batch. Args: db: Database session batch_id: ID of the batch to delete user_id: ID of the user deleting Returns: True if deleted successfully, False if not found """ batch = await ImportBatchProcessor.get_batch_status(db, batch_id) if not batch or batch.user_id != user_id: return False # Delete confirmation records stmt = delete(UserCardImportRecord).where(UserCardImportRecord.batch_id == batch_id) await db.execute(stmt) # Delete batch stmt = delete(CardImportBatch).where(CardImportBatch.id == batch_id) await db.execute(stmt) await db.flush() return True