import logging
import warnings
from pathlib import Path
from typing import Any, List, Optional

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from src.config.settings import settings
from src.core.entities.series import Serie
from src.core.utils.key_utils import generate_key_from_folder, is_valid_key
from src.server.database.service import AnimeSeriesService
from src.server.exceptions import (
    BadRequestError,
    NotFoundError,
    ServerError,
    ValidationError,
)
from src.server.models.anime import AnimeMetadataUpdate
from src.server.services.anime_service import AnimeService, AnimeServiceError
from src.server.services.background_loader_service import BackgroundLoaderService
from src.server.services.folder_rename_service import _scan_for_pre_existing_duplicates
from src.server.utils.dependencies import (
    get_anime_service,
    get_background_loader_service,
    get_database_session,
    get_optional_database_session,
    get_series_app,
    require_auth,
)
from src.server.utils.filesystem import sanitize_folder_name
from src.server.utils.validators import validate_filter_value, validate_search_query

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/anime", tags=["anime"])


@router.get("/status")
async def get_anime_status(
    _auth: dict = Depends(require_auth),
    series_app: Any = Depends(get_series_app),
) -> dict:
    """Get anime library status information.

    Args:
        _auth: Ensures the caller is authenticated (value unused)
        series_app: Core `SeriesApp` instance provided via dependency

    Returns:
        Dict[str, Any]: Status information including directory and series count

    Raises:
        HTTPException: If status retrieval fails
    """
    try:
        directory = (
            getattr(series_app, "directory_to_search", "")
            if series_app else ""
        )
        
        # Get series count
        series_count = 0
        if series_app and hasattr(series_app, "list"):
            series = series_app.list.GetList()
            series_count = len(series) if series else 0
        
        return {
            "directory": directory,
            "series_count": series_count
        }
    except Exception as exc:
        raise ServerError(
            message=f"Failed to get status: {str(exc)}"
        ) from exc


class DuplicateFolderGroup(BaseModel):
    """A group of duplicate folders for the same series.

    Attributes:
        key: Series key (provider-assigned unique identifier)
        folders: List of folder names that are duplicates
        folder_count: Number of duplicate folders
    """
    key: str = Field(..., description="Series key (unique identifier)")
    folders: List[str] = Field(..., description="List of duplicate folder names")
    folder_count: int = Field(..., description="Number of duplicate folders")


class DuplicateFoldersResponse(BaseModel):
    """Response model for duplicate folders listing.

    Attributes:
        total_groups: Total number of duplicate groups found
        duplicate_groups: List of duplicate folder groups
        message: Human-readable summary
    """
    total_groups: int = Field(..., description="Total number of duplicate groups")
    duplicate_groups: List[DuplicateFolderGroup] = Field(
        ..., description="List of duplicate folder groups"
    )
    message: str = Field(..., description="Human-readable summary")


@router.get("/duplicate-folders", response_model=DuplicateFoldersResponse)
async def get_duplicate_folders(
    _auth: dict = Depends(require_auth),
) -> DuplicateFoldersResponse:
    """List all pre-existing duplicate folder groups.

    Scans the anime directory for folders with tvshow.nfo files that
    map to the same series key. Returns groups of duplicates for
    manual review and cleanup.

    Returns:
        DuplicateFoldersResponse with groups of duplicate folders

    Note:
        Not all duplicate folders are safe to merge - some may belong
        to different releases (e.g., dubbed vs. subbed). Review carefully
        before taking action.
    """
    try:
        if not settings.anime_directory:
            return DuplicateFoldersResponse(
                total_groups=0,
                duplicate_groups=[],
                message="Anime directory not configured",
            )

        anime_dir = Path(settings.anime_directory)
        if not anime_dir.is_dir():
            return DuplicateFoldersResponse(
                total_groups=0,
                duplicate_groups=[],
                message=f"Anime directory not found: {anime_dir}",
            )

        duplicates = _scan_for_pre_existing_duplicates(anime_dir)

        groups = [
            DuplicateFolderGroup(
                key=dup.key,
                folders=dup.folders,
                folder_count=dup.count,
            )
            for dup in duplicates
        ]

        if groups:
            message = (
                f"Found {len(groups)} duplicate group(s). "
                "Review carefully - some duplicates may be different releases "
                "(e.g., dubbed vs. subbed)."
            )
        else:
            message = "No duplicate folders found."

        return DuplicateFoldersResponse(
            total_groups=len(groups),
            duplicate_groups=groups,
            message=message,
        )
    except Exception as exc:
        logger.error("Failed to scan for duplicate folders: %s", str(exc))
        raise ServerError(
            message=f"Failed to scan for duplicates: {str(exc)}"
        ) from exc


class AnimeSummary(BaseModel):
    """Summary of an anime series with missing episodes.
    
    The `key` field is the unique provider-assigned identifier used for all
    lookups and operations (URL-safe, e.g., "attack-on-titan").
    
    The `folder` field is metadata only for filesystem operations and display
    (e.g., "Attack on Titan (2013)") - not used for identification.
    
    Attributes:
        key: Unique series identifier (primary key for all operations)
        name: Display name of the series
        site: Provider site URL
        folder: Filesystem folder name (metadata only)
        missing_episodes: Episode dictionary mapping seasons to episode numbers
        has_missing: Boolean flag indicating if series has missing episodes
        link: Optional link to the series page (used when adding new series)
        has_nfo: Whether the series has NFO metadata
        nfo_created_at: ISO timestamp when NFO was created
        nfo_updated_at: ISO timestamp when NFO was last updated
        tmdb_id: The Movie Database (TMDB) ID
        tvdb_id: TheTVDB ID
    """
    key: str = Field(
        ...,
        description="Unique series identifier (primary key for all operations)"
    )
    name: str = Field(
        ...,
        description="Display name of the series"
    )
    site: str = Field(
        ...,
        description="Provider site URL"
    )
    folder: str = Field(
        ...,
        description="Filesystem folder name (metadata, not for lookups)"
    )
    missing_episodes: dict = Field(
        ...,
        description="Episode dictionary: {season: [episode_numbers]}"
    )
    has_missing: bool = Field(
        default=False,
        description="Whether the series has any missing episodes"
    )
    link: Optional[str] = Field(
        default="",
        description="Link to the series page (for adding new series)"
    )
    has_nfo: bool = Field(
        default=False,
        description="Whether the series has NFO metadata"
    )
    nfo_created_at: Optional[str] = Field(
        default=None,
        description="ISO timestamp when NFO was created"
    )
    nfo_updated_at: Optional[str] = Field(
        default=None,
        description="ISO timestamp when NFO was last updated"
    )
    tmdb_id: Optional[int] = Field(
        default=None,
        description="The Movie Database (TMDB) ID"
    )
    tvdb_id: Optional[int] = Field(
        default=None,
        description="TheTVDB ID"
    )
    
    class Config:
        """Pydantic model configuration."""
        json_schema_extra = {
            "example": {
                "key": "beheneko-the-elf-girls-cat",
                "name": "Beheneko",
                "site": "aniworld.to",
                "folder": "beheneko the elf girls cat (2025)",
                "missing_episodes": {"1": [1, 2, 3, 4]},
                "has_missing": True,
                "link": "https://aniworld.to/anime/stream/beheneko",
                "has_nfo": True,
                "nfo_created_at": "2025-01-15T10:30:00Z",
                "nfo_updated_at": "2025-01-15T10:30:00Z",
                "tmdb_id": 12345,
                "tvdb_id": 67890
            }
        }


class AnimeDetail(BaseModel):
    """Detailed information about a specific anime series.
    
    The `key` field is the unique provider-assigned identifier used for all
    lookups and operations (URL-safe, e.g., "attack-on-titan").
    
    The `folder` field is metadata only for filesystem operations and display.
    
    Attributes:
        key: Unique series identifier (primary key for all operations)
        title: Display name of the series
        folder: Filesystem folder name (metadata only)
        episodes: List of episode identifiers in "season-episode" format
        description: Optional description of the series
    """
    key: str = Field(
        ...,
        description="Unique series identifier (primary key for all operations)"
    )
    title: str = Field(
        ...,
        description="Display name of the series"
    )
    folder: str = Field(
        default="",
        description="Filesystem folder name (metadata, not for lookups)"
    )
    episodes: List[str] = Field(
        ...,
        description="List of episode identifiers in 'season-episode' format"
    )
    description: Optional[str] = Field(
        default=None,
        description="Optional description of the series"
    )
    
    class Config:
        """Pydantic model configuration."""
        json_schema_extra = {
            "example": {
                "key": "attack-on-titan",
                "title": "Attack on Titan",
                "folder": "Attack on Titan (2013)",
                "episodes": ["1-1", "1-2", "1-3"],
                "description": "Humans fight against giant humanoid Titans."
            }
        }


@router.get("/", response_model=List[AnimeSummary])
@router.get("", response_model=List[AnimeSummary])
async def list_anime(
    page: Optional[int] = 1,
    per_page: Optional[int] = 20,
    sort_by: Optional[str] = None,
    filter: Optional[str] = None,
    _auth: dict = Depends(require_auth),
    anime_service: AnimeService = Depends(get_anime_service),
) -> List[AnimeSummary]:
    """List all library series with their missing episodes status.

    Returns AnimeSummary objects where `key` is the primary identifier
    used for all operations. The `folder` field is metadata only and
    should not be used for lookups.
    
    All series are returned, with `has_missing` flag indicating whether
    a series has any missing episodes.

    Args:
        page: Page number for pagination (must be positive)
        per_page: Items per page (must be positive, max 1000)
        sort_by: Optional sorting parameter. Allowed: title, id, name,
            missing_episodes
        filter: Optional filter parameter. Allowed values:
            - "missing_episodes": Show only series that have any missing episodes
            - "no_episodes": Show only series that have no downloaded episodes
        _auth: Ensures the caller is authenticated (value unused)
        anime_service: AnimeService instance provided via dependency

    Returns:
        List[AnimeSummary]: Summary entries with `key` as primary identifier.
            Each entry includes:
            - key: Unique series identifier (use for all operations)
            - name: Display name
            - site: Provider site
            - folder: Filesystem folder name (metadata only)
            - missing_episodes: Dict mapping seasons to episode numbers
            - has_missing: Whether the series has any missing episodes

    Raises:
        HTTPException: When the underlying lookup fails or params invalid.
    """
    # Validate pagination parameters
    if page is not None:
        try:
            page_num = int(page)
            if page_num < 1:
                raise ValidationError(
                    message="Page number must be positive"
                )
            page = page_num
        except (ValueError, TypeError):
            raise ValidationError(
                message="Page must be a valid number"
            )
    
    if per_page is not None:
        try:
            per_page_num = int(per_page)
            if per_page_num < 1:
                raise ValidationError(
                    message="Per page must be positive"
                )
            if per_page_num > 1000:
                raise ValidationError(
                    message="Per page cannot exceed 1000"
                )
            per_page = per_page_num
        except (ValueError, TypeError):
            raise ValidationError(
                message="Per page must be a valid number"
            )
    
    # Validate sort_by parameter to prevent ORM injection
    if sort_by:
        # Only allow safe sort fields
        allowed_sort_fields = ["title", "id", "missing_episodes", "name"]
        if sort_by not in allowed_sort_fields:
            allowed = ", ".join(allowed_sort_fields)
            raise ValidationError(
                message=f"Invalid sort_by parameter. Allowed: {allowed}"
            )
    
    # Validate filter parameter
    if filter:
        try:
            allowed_filters = ["missing_episodes", "no_episodes"]
            validate_filter_value(filter, allowed_filters)
        except ValueError as e:
            raise ValidationError(message=str(e))
    
    try:
        # Use AnimeService to get series with metadata from database
        series_list = await anime_service.list_series_with_filters(
            filter_type=filter
        )
        
        summaries: List[AnimeSummary] = []
        for series_dict in series_list:
            # Convert episode dict keys to strings for JSON serialization
            episode_dict = series_dict.get("episodeDict", {}) or {}
            missing_episodes = {str(k): v for k, v in episode_dict.items()}
            
            # Determine if series has missing episodes
            has_missing = bool(episode_dict)
            
            summaries.append(
                AnimeSummary(
                    key=series_dict["key"],
                    name=series_dict["name"],
                    site=series_dict["site"],
                    folder=series_dict["folder"],
                    missing_episodes=missing_episodes,
                    has_missing=has_missing,
                    has_nfo=series_dict.get("has_nfo", False),
                    nfo_created_at=series_dict.get("nfo_created_at"),
                    nfo_updated_at=series_dict.get("nfo_updated_at"),
                    tmdb_id=series_dict.get("tmdb_id"),
                    tvdb_id=series_dict.get("tvdb_id"),
                )
            )
        
        # Apply sorting if requested
        if sort_by:
            if sort_by in ["title", "name"]:
                summaries.sort(key=lambda x: x.name or x.key)
            elif sort_by == "id":
                summaries.sort(key=lambda x: x.key)
            elif sort_by == "missing_episodes":
                # Sort by total number of missing episodes
                # (count all episodes across all seasons)
                summaries.sort(
                    key=lambda x: sum(
                        len(eps) for eps in x.missing_episodes.values()
                    ),
                    reverse=True
                )
        
        return summaries
    except (ValidationError, BadRequestError, NotFoundError, ServerError):
        raise
    except Exception as exc:
        raise ServerError(
            message="Failed to retrieve anime list"
        ) from exc


@router.post("/rescan")
async def trigger_rescan(
    _auth: dict = Depends(require_auth),
    anime_service: AnimeService = Depends(get_anime_service),
) -> dict:
    """Kick off a rescan of the local library.

    Args:
        _auth: Ensures the caller is authenticated (value unused)
        anime_service: AnimeService instance provided via dependency.

    Returns:
        Dict[str, Any]: Status payload confirming scan started

    Raises:
        HTTPException: If the rescan command fails.
    """
    try:
        # Use the async rescan method from AnimeService
        # Progress tracking is handled automatically via event handlers
        await anime_service.rescan()
        
        return {
            "success": True,
            "message": "Rescan started successfully",
        }
    except AnimeServiceError as e:
        raise ServerError(
            message=f"Rescan failed: {str(e)}"
        ) from e
    except Exception as exc:
        raise ServerError(
            message="Failed to start rescan"
        ) from exc


@router.get("/scan/status")
async def get_scan_status(
    _auth: dict = Depends(require_auth),
    anime_service: AnimeService = Depends(get_anime_service),
) -> dict:
    """Get the current scan status.

    Returns the current state of any ongoing library scan,
    useful for restoring UI state after page reload.

    Args:
        _auth: Ensures the caller is authenticated (value unused)
        anime_service: AnimeService instance provided via dependency.

    Returns:
        Dict[str, Any]: Current scan status including:
            - is_scanning: Whether a scan is in progress
            - total_items: Total items to scan
            - directories_scanned: Items scanned so far
            - current_directory: Current item being scanned
            - directory: Root scan directory
    """
    return anime_service.get_scan_status()


class AddSeriesRequest(BaseModel):
    """Request model for adding a new series."""
    
    link: str
    name: str


def _validate_search_query_extended(query: str) -> str:
    """Validate and sanitize search query with additional checks.
    
    Args:
        query: The search query string
        
    Returns:
        str: The validated query
        
    Raises:
        HTTPException: If query is invalid
    """
    if not query or not query.strip():
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Search query cannot be empty"
        )
    
    # Check for null bytes
    if "\x00" in query:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Null bytes not allowed in query"
        )
    
    # Limit query length to prevent abuse
    if len(query) > 200:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail="Search query too long (max 200 characters)"
        )
    
    # Validate and normalize the search query using utility function
    try:
        normalized = validate_search_query(query)
        return normalized
    except ValueError as e:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=str(e)
        )



class SearchAnimeRequest(BaseModel):
    """Request model for searching anime."""
    query: str = Field(..., min_length=1, description="Search query string")


@router.get("/search", response_model=List[AnimeSummary])
async def search_anime_get(
    query: str,
    series_app: Optional[Any] = Depends(get_series_app),
) -> List[AnimeSummary]:
    """Search the provider for additional series matching a query (GET).

    Returns AnimeSummary objects where `key` is the primary identifier.
    Use the `key` field for subsequent operations (add, download, etc.).

    Args:
        query: Search term passed as query parameter
        series_app: Optional SeriesApp instance provided via dependency.

    Returns:
        List[AnimeSummary]: Discovered matches with `key` as identifier.

    Raises:
        HTTPException: When provider communication fails or query is invalid.
    """
    return await _perform_search(query, series_app)


@router.post(
    "/search",
    response_model=List[AnimeSummary],
)
async def search_anime_post(
    request: SearchAnimeRequest,
    series_app: Optional[Any] = Depends(get_series_app),
) -> List[AnimeSummary]:
    """Search the provider for additional series matching a query (POST).

    Returns AnimeSummary objects where `key` is the primary identifier.
    Use the `key` field for subsequent operations (add, download, etc.).

    Args:
        request: Request containing the search query
        series_app: Optional SeriesApp instance provided via dependency.

    Returns:
        List[AnimeSummary]: Discovered matches with `key` as identifier.

    Raises:
        HTTPException: When provider communication fails or query is invalid.
    """
    return await _perform_search(request.query, series_app)


async def _perform_search(
    query: str,
    series_app: Optional[Any],
) -> List[AnimeSummary]:
    """Search for anime series matching the given query.

    This internal function performs the actual search logic, extracting
    results from the provider and converting them to AnimeSummary objects.

    The returned summaries use `key` as the primary identifier. The `key`
    is extracted from the result's key field (preferred) or derived from
    the link URL if not available. The `folder` field is metadata only.

    Args:
        query: Search term (will be validated and sanitized)
        series_app: Optional SeriesApp instance for search.

    Returns:
        List[AnimeSummary]: Discovered matches with `key` as identifier
            and `folder` as metadata. Each summary includes:
            - key: Unique series identifier (primary)
            - name: Display name
            - site: Provider site
            - folder: Filesystem folder name (metadata)
            - link: URL to series page
            - missing_episodes: Episode dictionary

    Raises:
        HTTPException: When provider communication fails or query is invalid.
    """
    try:
        # Validate and sanitize the query
        validated_query = _validate_search_query_extended(query)
        
        # Check if series_app is available
        if not series_app:
            # Return empty list if service unavailable
            # Tests can verify validation without needing a real series_app
            return []
        
        matches: List[Any] = []
        if hasattr(series_app, "search"):
            # SeriesApp.search is async; await the result
            matches = await series_app.search(validated_query)

        summaries: List[AnimeSummary] = []
        for match in matches:
            if isinstance(match, dict):
                # Extract key (primary identifier)
                key = match.get("key") or match.get("id") or ""
                title = match.get("title") or match.get("name") or ""
                site = match.get("site") or ""
                folder = match.get("folder") or ""
                link = match.get("link") or match.get("url") or ""
                missing = (
                    match.get("missing_episodes")
                    or match.get("missing")
                    or {}
                )
                
                # If key is empty, try to extract from link
                if not key and link:
                    if "/anime/stream/" in link:
                        key = link.split("/anime/stream/")[-1].split("/")[0]
                    elif link and "/" not in link:
                        # Link is just a slug (e.g., "attack-on-titan")
                        key = link
            else:
                # Extract key (primary identifier)
                key = getattr(match, "key", "") or getattr(match, "id", "")
                title = getattr(match, "title", "") or getattr(
                    match, "name", ""
                )
                site = getattr(match, "site", "")
                folder = getattr(match, "folder", "")
                link = getattr(match, "link", "") or getattr(
                    match, "url", ""
                )
                missing = getattr(match, "missing_episodes", {})
                
                # If key is empty, try to extract from link
                if not key and link:
                    if "/anime/stream/" in link:
                        key = link.split("/anime/stream/")[-1].split("/")[0]
                    elif link and "/" not in link:
                        # Link is just a slug (e.g., "attack-on-titan")
                        key = link

            summaries.append(
                AnimeSummary(
                    key=key,
                    name=title,
                    site=site,
                    folder=folder,
                    link=link,
                    missing_episodes=missing,
                )
            )

        return summaries
    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Search failed",
        ) from exc


@router.post("/add", status_code=status.HTTP_202_ACCEPTED)
async def add_series(
    request: AddSeriesRequest,
    _auth: dict = Depends(require_auth),
    series_app: Any = Depends(get_series_app),
    anime_service: AnimeService = Depends(get_anime_service),
    db: Optional[AsyncSession] = Depends(get_optional_database_session),
    background_loader: BackgroundLoaderService = Depends(get_background_loader_service),
) -> dict:
    """Add a new series to the library with asynchronous data loading.

    This endpoint performs immediate series addition and queues background loading:
    1. Validates inputs and extracts the series key from the link URL
    2. Creates a sanitized folder name from the display name
    3. Saves the series to the database with loading_status="pending"
    4. Creates the folder on disk with the sanitized name
    5. Queues background loading task for episodes, NFO, and images
    6. Returns immediately (202 Accepted) without waiting for data loading
    
    Data loading happens asynchronously in the background, with real-time
    status updates via WebSocket.

    The `key` is the URL-safe identifier used for all lookups.
    The `name` is stored as display metadata and used to derive
    the filesystem folder name (sanitized for filesystem safety).

    Args:
        request: Request containing the series link and name.
            - link: URL to the series (e.g., aniworld.to/anime/stream/key)
            - name: Display name for the series
        _auth: Ensures the caller is authenticated (value unused)
        series_app: Core `SeriesApp` instance provided via dependency
        db: Optional database session for async operations
        background_loader: BackgroundLoaderService for async data loading

    Returns:
        Dict[str, Any]: Status payload with:
            - status: "success" or "exists"
            - message: Human-readable status message
            - key: Series unique identifier
            - folder: Created folder path
            - db_id: Database ID (if saved to DB)
            - loading_status: Current loading status
            - loading_progress: Dict of what data is being loaded

    Raises:
        HTTPException: If adding the series fails or link is invalid
    """
    try:
        # Step A: Validate inputs
        if not request.link or not request.link.strip():
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Series link cannot be empty",
            )
        
        if not request.name or not request.name.strip():
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Series name cannot be empty",
            )
        
        # Extract key from link URL
        # Expected format: https://aniworld.to/anime/stream/{key}
        link = request.link.strip()
        key = link
        
        # Try to extract key from URL path
        if "/anime/stream/" in link:
            # Extract everything after /anime/stream/
            key = link.split("/anime/stream/")[-1].split("/")[0].strip()
        elif "/" in link:
            # Fallback: use last path segment
            key = link.rstrip("/").split("/")[-1].strip()
        
        # Validate extracted key
        if not key:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Could not extract series key from link",
            )
        
        # Step B: Fetch year from provider and create folder name with year
        name = request.name.strip()
        
        # Fetch year from provider
        year = None
        if series_app and hasattr(series_app, 'loader'):
            try:
                year = series_app.loader.get_year(key)
                logger.info("Fetched year for %s: %s", key, year)
            except Exception as e:
                logger.warning("Could not fetch year for %s: %s", key, e)
        
        # Create folder name with year if available
        if year:
            year_suffix = f" ({year})"
            if name.endswith(year_suffix):
                folder_name_with_year = name
            else:
                folder_name_with_year = f"{name}{year_suffix}"
        else:
            folder_name_with_year = name
        
        try:
            folder = sanitize_folder_name(folder_name_with_year)
        except ValueError as e:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Invalid series name for folder: {str(e)}",
            )
        
        db_id = None
        
        # Step C: Save to database if available
        if db is not None:
            # Check if series already exists in database
            existing = await AnimeSeriesService.get_by_key(db, key)
            if existing:
                return {
                    "status": "exists",
                    "message": f"Series already exists: {name}",
                    "key": key,
                    "folder": existing.folder,
                    "db_id": existing.id,
                    "loading_status": existing.loading_status,
                    "loading_progress": {
                        "episodes": existing.episodes_loaded,
                        "nfo": existing.has_nfo,
                        "logo": existing.logo_loaded,
                        "images": existing.images_loaded
                    }
                }
            
            # Save to database using AnimeSeriesService with loading status
            anime_series = await AnimeSeriesService.create(
                db=db,
                key=key,
                name=name,
                site="aniworld.to",
                folder=folder,
                year=year,
                loading_status="pending",
                episodes_loaded=False,
                logo_loaded=False,
                images_loaded=False,
                loading_started_at=None,
            )
            db_id = anime_series.id
            
            logger.info(
                "Added series to database: %s (key=%s, db_id=%d, year=%s, loading=pending)",
                name,
                key,
                db_id,
                year
            )
        
        # Step D: Add to SerieList (in-memory only, no folder creation)
        if series_app and hasattr(series_app, "list"):
            serie = Serie(
                key=key,
                name=name,
                site="aniworld.to",
                folder=folder,
                episodeDict={},
                year=year
            )
            
            # Add to in-memory cache without creating folder on disk
            if hasattr(series_app.list, 'keyDict'):
                series_app.list.keyDict[key] = serie
                logger.info(
                    "Added series to in-memory cache: %s (key=%s, folder=%s, year=%s)",
                    name,
                    key,
                    folder,
                    year
                )
        
        # Step E: Queue background loading task for episodes, NFO, and images
        try:
            await background_loader.add_series_loading_task(
                key=key,
                folder=folder,
                name=name,
                year=year
            )
            logger.info(
                "Queued background loading for %s (key=%s)",
                name,
                key
            )
        except Exception as e:
            # Background loading queue failure is not critical - series was still added
            logger.warning(
                "Failed to queue background loading for %s: %s",
                key,
                e
            )

        # Step F: Scan missing episodes immediately if background loader is not running
        # Uses existing SerieScanner and AnimeService sync to avoid duplicates
        try:
            loader_running = bool(
                background_loader.worker_tasks
                and any(not t.done() for t in background_loader.worker_tasks)
            )
            if (
                not loader_running
                and series_app
                and hasattr(series_app, "serie_scanner")
            ):
                missing_episodes = series_app.serie_scanner.scan_single_series(
                    key=key,
                    folder=folder
                )
                total_missing = sum(
                    len(eps) for eps in missing_episodes.values()
                )
                logger.info(
                    "Scanned %d missing episodes for %s",
                    total_missing,
                    key
                )
                
                # Persist scan results to database (includes episodes)
                # scan_single_series updates serie_scanner.keyDict with episodeDict
                # sync_single_series_after_scan retrieves from there and saves to DB
                await anime_service.sync_single_series_after_scan(key)
        except Exception as e:
            logger.warning(
                "Failed to scan missing episodes for %s: %s",
                key,
                e
            )
        
        # Step G: Return immediate response (202 Accepted)
        response = {
            "status": "success",
            "message": f"Series added successfully: {name}. Data will be loaded in background.",
            "key": key,
            "folder": folder,
            "db_id": db_id,
            "loading_status": "pending",
            "loading_progress": {
                "episodes": False,
                "nfo": False,
                "logo": False,
                "images": False
            }
        }
        
        return response
        
    except HTTPException:
        raise
    except Exception as exc:
        logger.error("Failed to add series: %s", exc, exc_info=True)
        
        # Attempt to rollback database entry if folder creation failed
        # (This is a best-effort cleanup)
        
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to add series: {str(exc)}",
        ) from exc


@router.get("/{anime_key}/loading-status")
async def get_loading_status(
    anime_key: str,
    _auth: dict = Depends(require_auth),
    db: Optional[AsyncSession] = Depends(get_optional_database_session),
) -> dict:
    """Get current loading status for a series.

    Returns the current background loading status including what data
    has been loaded and what is still pending.

    Args:
        anime_key: Series unique identifier (key)
        _auth: Ensures the caller is authenticated
        db: Optional database session

    Returns:
        Dict with loading status information:
            - key: Series identifier
            - loading_status: Current status (pending, loading_*, completed, failed)
            - progress: Dict of what data is loaded
            - started_at: When loading started
            - completed_at: When loading completed (if done)
            - message: Human-readable status message
            - error: Error message if failed

    Raises:
        HTTPException: If series not found or database unavailable
    """
    if db is None:
        raise HTTPException(
            status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
            detail="Database not available"
        )
    
    try:
        from src.server.database.service import AnimeSeriesService

        # Get series from database
        series = await AnimeSeriesService.get_by_key(db, anime_key)
        
        if not series:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Series not found: {anime_key}"
            )
        
        # Build status message
        message = ""
        if series.loading_status == "pending":
            message = "Queued for loading..."
        elif series.loading_status == "loading_episodes":
            message = "Loading episodes..."
        elif series.loading_status == "loading_nfo":
            message = "Generating NFO file..."
        elif series.loading_status == "loading_logo":
            message = "Downloading logo..."
        elif series.loading_status == "loading_images":
            message = "Downloading images..."
        elif series.loading_status == "completed":
            message = "All data loaded successfully"
        elif series.loading_status == "failed":
            message = f"Loading failed: {series.loading_error}"
        else:
            message = "Loading..."
        
        return {
            "key": series.key,
            "loading_status": series.loading_status,
            "progress": {
                "episodes": series.episodes_loaded,
                "nfo": series.has_nfo,
                "logo": series.logo_loaded,
                "images": series.images_loaded
            },
            "started_at": series.loading_started_at.isoformat() if series.loading_started_at else None,
            "completed_at": series.loading_completed_at.isoformat() if series.loading_completed_at else None,
            "message": message,
            "error": series.loading_error
        }
        
    except HTTPException:
        raise
    except Exception as exc:
        logger.error("Failed to get loading status: %s", exc, exc_info=True)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Failed to get loading status: {str(exc)}"
        ) from exc


@router.get("/{anime_id}", response_model=AnimeDetail)
async def get_anime(
    anime_id: str,
    series_app: Optional[Any] = Depends(get_series_app)
) -> AnimeDetail:
    """Return detailed information about a specific series.

    The `anime_id` parameter should be the series `key` (primary identifier).
    For backward compatibility, lookups by `folder` are also supported but
    deprecated. The `key` is checked first, then `folder` as fallback.

    Args:
        anime_id: Series `key` (primary) or `folder` (deprecated fallback).
        series_app: Optional SeriesApp instance provided via dependency.

    Returns:
        AnimeDetail: Detailed series metadata including episode list.
            Response includes `key` as the primary identifier and
            `folder` as metadata.

    Raises:
        HTTPException: If the anime cannot be located or retrieval fails.
    """
    try:
        # Check if series_app is available
        if not series_app or not hasattr(series_app, "list"):
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Series not found",
            )
        
        series = series_app.list.GetList()
        found = None
        
        # Primary lookup: search by key first (preferred)
        for serie in series:
            if getattr(serie, "key", None) == anime_id:
                found = serie
                break
        
        # Fallback lookup: search by folder (backward compatibility)
        if not found:
            for serie in series:
                if getattr(serie, "folder", None) == anime_id:
                    found = serie
                    # Log deprecation warning for folder-based lookup
                    key = getattr(serie, "key", "unknown")
                    logger.warning(
                        "Folder-based lookup for '%s' is deprecated. "
                        "Use series key '%s' instead. Folder-based lookups "
                        "will be removed in v3.0.0.",
                        anime_id,
                        key
                    )
                    warnings.warn(
                        f"Folder-based lookup for '{anime_id}' is deprecated. "
                        f"Use series key '{key}' instead.",
                        DeprecationWarning,
                        stacklevel=2
                    )
                    break

        if not found:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail="Series not found",
            )

        episodes: List[str] = []
        episode_dict = getattr(found, "episodeDict", {}) or {}
        for season, episode_numbers in episode_dict.items():
            for episode in episode_numbers:
                episodes.append(f"{season}-{episode}")

        # Return AnimeDetail with key as the primary identifier
        return AnimeDetail(
            key=getattr(found, "key", ""),
            title=getattr(found, "name", ""),
            folder=getattr(found, "folder", ""),
            episodes=episodes,
            description=getattr(found, "description", None),
        )
    except HTTPException:
        raise
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Failed to retrieve series details",
        ) from exc


# Maximum allowed input size for security
MAX_INPUT_LENGTH = 100000  # 100KB


@router.put("/{anime_key}")
async def update_anime_metadata(
    anime_key: str,
    body: AnimeMetadataUpdate,
    _auth: dict = Depends(require_auth),
    db: AsyncSession = Depends(get_database_session),
) -> dict:
    """Update anime metadata (key, tmdb_id, tvdb_id).

    Args:
        anime_key: Current series key to update
        body: Fields to update (all optional)
        _auth: Authentication dependency
        db: Database session

    Returns:
        Updated series metadata

    Raises:
        HTTPException 404: Series not found
        HTTPException 409: Key conflict (new key already exists)
        HTTPException 422: Validation error
    """
    series = await AnimeSeriesService.get_by_key(db, anime_key)
    if not series:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Series with key '{anime_key}' not found",
        )

    updates = {}

    if body.key is not None and body.key != anime_key:
        existing = await AnimeSeriesService.get_by_key(db, body.key)
        if existing:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"A series with key '{body.key}' already exists",
            )
        updates["key"] = body.key

    if body.tmdb_id is not None:
        updates["tmdb_id"] = body.tmdb_id

    if body.tvdb_id is not None:
        updates["tvdb_id"] = body.tvdb_id

    if not updates:
        return {
            "key": series.key,
            "tmdb_id": series.tmdb_id,
            "tvdb_id": series.tvdb_id,
            "message": "No changes",
        }

    updated = await AnimeSeriesService.update(db, series.id, **updates)
    await db.commit()

    logger.info(
        "Updated metadata for '%s': %s",
        anime_key,
        updates,
    )

    return {
        "key": updated.key,
        "tmdb_id": updated.tmdb_id,
        "tvdb_id": updated.tvdb_id,
        "message": "Metadata updated successfully",
    }

