from typing import List, Optional from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from src.server.utils.dependencies import get_series_app, require_auth router = APIRouter(prefix="/api/v1/anime", tags=["anime"]) class AnimeSummary(BaseModel): id: str title: str missing_episodes: int class AnimeDetail(BaseModel): id: str title: str episodes: List[str] description: Optional[str] = None @router.get("/", response_model=List[AnimeSummary]) async def list_anime( _auth: dict = Depends(require_auth), series_app=Depends(get_series_app) ): """List series with missing episodes using the core SeriesApp.""" try: series = series_app.List.GetMissingEpisode() result = [] for s in series: missing = 0 try: missing = len(s.episodeDict) if getattr(s, "episodeDict", None) is not None else 0 except Exception: missing = 0 result.append(AnimeSummary(id=getattr(s, "key", getattr(s, "folder", "")), title=getattr(s, "name", ""), missing_episodes=missing)) return result except HTTPException: raise except Exception: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve anime list") @router.post("/rescan") async def trigger_rescan(series_app=Depends(get_series_app)): """Trigger a rescan of local series data using SeriesApp.ReScan.""" try: # SeriesApp.ReScan expects a callback; pass a no-op if hasattr(series_app, "ReScan"): series_app.ReScan(lambda *args, **kwargs: None) return {"success": True, "message": "Rescan started"} else: raise HTTPException(status_code=status.HTTP_501_NOT_IMPLEMENTED, detail="Rescan not available") except HTTPException: raise except Exception: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to start rescan") class SearchRequest(BaseModel): query: str @router.post("/search", response_model=List[AnimeSummary]) async def search_anime(request: SearchRequest, series_app=Depends(get_series_app)): """Search for new anime by query text using the SeriesApp loader.""" try: matches = [] if hasattr(series_app, "search"): # SeriesApp.search is synchronous in core; call directly matches = series_app.search(request.query) result = [] for m in matches: # matches may be dicts or objects if isinstance(m, dict): mid = m.get("key") or m.get("id") or "" title = m.get("title") or m.get("name") or "" missing = int(m.get("missing", 0)) if m.get("missing") is not None else 0 else: mid = getattr(m, "key", getattr(m, "id", "")) title = getattr(m, "title", getattr(m, "name", "")) missing = int(getattr(m, "missing", 0)) result.append(AnimeSummary(id=mid, title=title, missing_episodes=missing)) return result except HTTPException: raise except Exception: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Search failed") @router.get("/{anime_id}", response_model=AnimeDetail) async def get_anime(anime_id: str, series_app=Depends(get_series_app)): """Return detailed info about a series from SeriesApp.List.""" try: series = series_app.List.GetList() found = None for s in series: if getattr(s, "key", None) == anime_id or getattr(s, "folder", None) == anime_id: found = s break if not found: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Series not found") episodes = [] epdict = getattr(found, "episodeDict", {}) or {} for season, eps in epdict.items(): for e in eps: episodes.append(f"{season}-{e}") return AnimeDetail(id=getattr(found, "key", getattr(found, "folder", "")), title=getattr(found, "name", ""), episodes=episodes, description=getattr(found, "description", None)) except HTTPException: raise except Exception: raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to retrieve series details")