"""Sound management API endpoints.""" from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlmodel.ext.asyncio.session import AsyncSession from app.core.database import get_db, get_session_factory from app.core.dependencies import get_current_active_user_flexible from app.models.credit_action import CreditActionType from app.models.sound import Sound from app.models.user import User from app.repositories.sound import SortOrder, SoundRepository, SoundSortField from app.schemas.sound import SoundResponse, SoundsListResponse from app.services.credit import CreditService, InsufficientCreditsError from app.services.favorite import FavoriteService from app.services.vlc_player import VLCPlayerService, get_vlc_player_service router = APIRouter(prefix="/sounds", tags=["sounds"]) def get_vlc_player() -> VLCPlayerService: """Get the VLC player service.""" return get_vlc_player_service(get_session_factory()) def get_credit_service() -> CreditService: """Get the credit service.""" return CreditService(get_session_factory()) def get_favorite_service() -> FavoriteService: """Get the favorite service.""" return FavoriteService(get_session_factory()) async def get_sound_repository( session: Annotated[AsyncSession, Depends(get_db)], ) -> SoundRepository: """Get the sound repository.""" return SoundRepository(session) @router.get("/", response_model=SoundsListResponse) async def get_sounds( # noqa: PLR0913 current_user: Annotated[User, Depends(get_current_active_user_flexible)], sound_repo: Annotated[SoundRepository, Depends(get_sound_repository)], favorite_service: Annotated[FavoriteService, Depends(get_favorite_service)], types: Annotated[ list[str] | None, Query(description="Filter by sound types (e.g., SDB, TTS, EXT)"), ] = None, search: Annotated[ str | None, Query(description="Search sounds by name"), ] = None, sort_by: Annotated[ SoundSortField | None, Query(description="Sort by field"), ] = None, sort_order: Annotated[ SortOrder, Query(description="Sort order (asc or desc)"), ] = SortOrder.ASC, limit: Annotated[ int | None, Query(description="Maximum number of results", ge=1, le=1000), ] = None, offset: Annotated[ int, Query(description="Number of results to skip", ge=0), ] = 0, ) -> SoundsListResponse: """Get sounds with optional search, filtering, and sorting.""" try: sounds = await sound_repo.search_and_sort( search_query=search, sound_types=types, sort_by=sort_by, sort_order=sort_order, limit=limit, offset=offset, ) # Add favorite indicators for each sound sound_responses = [] for sound in sounds: is_favorited = await favorite_service.is_sound_favorited(current_user.id, sound.id) favorite_count = await favorite_service.get_sound_favorite_count(sound.id) sound_response = SoundResponse.from_sound(sound, is_favorited, favorite_count) sound_responses.append(sound_response) except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to get sounds: {e!s}", ) from e else: return SoundsListResponse(sounds=sound_responses) # VLC PLAYER @router.post("/play/{sound_id}") async def play_sound_with_vlc( sound_id: int, current_user: Annotated[User, Depends(get_current_active_user_flexible)], vlc_player: Annotated[VLCPlayerService, Depends(get_vlc_player)], sound_repo: Annotated[SoundRepository, Depends(get_sound_repository)], credit_service: Annotated[CreditService, Depends(get_credit_service)], ) -> dict[str, str | int | bool]: """Play a sound using VLC subprocess (requires 1 credit).""" try: # Get the sound sound = await sound_repo.get_by_id(sound_id) if not sound: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Sound with ID {sound_id} not found", ) # Check and validate credits before playing try: await credit_service.validate_and_reserve_credits( current_user.id, CreditActionType.VLC_PLAY_SOUND, ) except InsufficientCreditsError as e: raise HTTPException( status_code=status.HTTP_402_PAYMENT_REQUIRED, detail=( f"Insufficient credits: {e.required} required, " f"{e.available} available" ), ) from e # Play the sound using VLC success = await vlc_player.play_sound(sound) # Deduct credits based on success await credit_service.deduct_credits( current_user.id, CreditActionType.VLC_PLAY_SOUND, success=success, metadata={"sound_id": sound_id, "sound_name": sound.name}, ) if not success: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to launch VLC for sound playback", ) except HTTPException: raise except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to play sound: {e!s}", ) from e else: return { "message": f"Sound '{sound.name}' is now playing via VLC", "sound_id": sound_id, "sound_name": sound.name, "success": True, "credits_deducted": 1, } @router.post("/stop") async def stop_all_vlc_instances( current_user: Annotated[User, Depends(get_current_active_user_flexible)], # noqa: ARG001 vlc_player: Annotated[VLCPlayerService, Depends(get_vlc_player)], ) -> dict: """Stop all running VLC instances.""" try: return await vlc_player.stop_all_vlc_instances() except Exception as e: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to stop VLC instances: {e!s}", ) from e