import asyncio import logging from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from ..dependencies import get_db, get_current_user, require_admin from ..models.user import User from ..models.media_item import LibraryItem from ..services.matcher import match_audiobook, search_for_item, _apply_match, _enrich_match from ..services.matching.musicbrainz import get_release_details from ..services.matching.open_library import get_work_details from ..services.matching.base import MatchResult from datetime import datetime logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/items", tags=["matching"]) @router.post("/{item_id}/match") async def trigger_match( item_id: str, background_tasks: BackgroundTasks, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") background_tasks.add_task(match_audiobook, item_id) return {"message": "Matching gestartet", "itemId": item_id} @router.get("/{item_id}/match/search") async def search_match( item_id: str, q: str | None = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") query = q or item.title or "" author = item.author if not q else None results = await search_for_item(query, author) return {"results": results} @router.post("/{item_id}/match/apply") async def apply_match( item_id: str, body: dict, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """ Wendet einen manuell gewählten Match-Treffer an. body: { source, id, title, author, narrator, description, publisher, publishYear, series, seriesSequence, language, genres, cover, ... } """ result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") source = body.get("source", "manual") source_id = body.get("id", "") logger.info( f"Manual apply: item={item_id} source={source} source_id={source_id} " f"body_keys={sorted(body.keys())}" ) # Immer aus body konstruieren (search_for_item liefert jetzt alle Felder) match_result = MatchResult( source=source, source_id=source_id, title=body.get("title") or item.title or "", subtitle=body.get("subtitle"), author=body.get("author"), narrator=body.get("narrator"), description=body.get("description"), publisher=body.get("publisher"), publish_year=body.get("publishYear"), series=body.get("series"), series_sequence=body.get("seriesSequence"), language=body.get("language"), genres=body.get("genres") or [], cover_url=body.get("cover"), confidence=1.0, ) # Mit Details anreichern (Beschreibung, Kapitel) — überschreibt keine vorhandenen Werte try: if source == "musicbrainz": details = await get_release_details(source_id) if details: _enrich_match(match_result, details) elif source == "open_library": details = await get_work_details(source_id) if details: _enrich_match(match_result, details) except Exception as e: logger.warning(f"Details-Laden fehlgeschlagen ({source}: {source_id}): {e}") match_result.confidence = 1.0 await _apply_match(db, item, match_result, confidence=1.0) item.match_locked = True item.updated_at = datetime.utcnow() await db.commit() await db.refresh(item) from ..routers.items import _enrich_item_with_files return await _enrich_item_with_files(item, db) @router.delete("/{item_id}/match") async def clear_match( item_id: str, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(LibraryItem).where(LibraryItem.id == item_id)) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Item not found") item.matched_source = "none" item.matched_id = None item.match_confidence = 0.0 item.match_locked = False tags = item.tags or [] if "zu_prüfen" not in tags: tags.append("zu_prüfen") item.tags = tags item.updated_at = datetime.utcnow() await db.commit() return {"success": True}