import logging

from fastapi import APIRouter, Body, Depends, HTTPException, Request
from fastapi.responses import Response, StreamingResponse

from api.v1.schemas.stream import (
    PlaybackSessionResponse,
    ProgressReportRequest,
    StartPlaybackRequest,
    StopReportRequest,
)
from core.dependencies import (
    get_jellyfin_playback_service,
    get_local_files_service,
    get_navidrome_playback_service,
    get_plex_playback_service,
)
from core.exceptions import ExternalServiceError, PlaybackNotAllowedError, ResourceNotFoundError
from infrastructure.msgspec_fastapi import MsgSpecBody, MsgSpecRoute
from services.jellyfin_playback_service import JellyfinPlaybackService
from services.local_files_service import LocalFilesService
from services.navidrome_playback_service import NavidromePlaybackService
from services.plex_playback_service import PlexPlaybackService

logger = logging.getLogger(__name__)

router = APIRouter(route_class=MsgSpecRoute, prefix="/stream", tags=["streaming"])


@router.get("/jellyfin/{item_id}")
async def stream_jellyfin_audio(
    item_id: str,
    request: Request,
    playback_service: JellyfinPlaybackService = Depends(get_jellyfin_playback_service),
) -> StreamingResponse:
    try:
        range_header = request.headers.get("Range")
        return await playback_service.proxy_stream(item_id, range_header=range_header)
    except ResourceNotFoundError:
        raise HTTPException(status_code=404, detail="Audio item not found")
    except PlaybackNotAllowedError as e:
        logger.warning("Playback not allowed for %s: %s", item_id, e)
        raise HTTPException(status_code=403, detail="Playback not allowed")
    except ExternalServiceError as e:
        if "416" in str(e):
            raise HTTPException(status_code=416, detail="Range not satisfiable")
        raise HTTPException(status_code=502, detail="Failed to stream from Jellyfin")


@router.head("/jellyfin/{item_id}")
async def head_jellyfin_audio(
    item_id: str,
    playback_service: JellyfinPlaybackService = Depends(get_jellyfin_playback_service),
) -> Response:
    try:
        return await playback_service.proxy_head(item_id)
    except ResourceNotFoundError:
        raise HTTPException(status_code=404, detail="Audio item not found")
    except PlaybackNotAllowedError as e:
        logger.warning("Playback not allowed for %s: %s", item_id, e)
        raise HTTPException(status_code=403, detail="Playback not allowed")
    except ExternalServiceError as e:
        logger.error("Jellyfin head stream error for %s: %s", item_id, e)
        raise HTTPException(status_code=502, detail="Failed to resolve Jellyfin stream")


@router.post("/jellyfin/{item_id}/start", response_model=PlaybackSessionResponse)
async def start_jellyfin_playback(
    item_id: str,
    body: StartPlaybackRequest | None = Body(default=None),
    playback_service: JellyfinPlaybackService = Depends(get_jellyfin_playback_service),
) -> PlaybackSessionResponse:
    try:
        play_session_id = await playback_service.start_playback(
            item_id,
            play_session_id=body.play_session_id if body else None,
        )
        return PlaybackSessionResponse(play_session_id=play_session_id, item_id=item_id)
    except ResourceNotFoundError:
        raise HTTPException(status_code=404, detail="Item not found")
    except PlaybackNotAllowedError as e:
        logger.warning("Playback not allowed for %s: %s", item_id, e)
        raise HTTPException(status_code=403, detail="Playback not allowed")
    except ExternalServiceError as e:
        logger.error("Failed to start playback for %s: %s", item_id, e)
        raise HTTPException(status_code=502, detail="Failed to start Jellyfin playback")


@router.post("/jellyfin/{item_id}/progress", status_code=204)
async def report_jellyfin_progress(
    item_id: str,
    body: ProgressReportRequest = MsgSpecBody(ProgressReportRequest),
    playback_service: JellyfinPlaybackService = Depends(get_jellyfin_playback_service),
) -> Response:
    try:
        await playback_service.report_progress(
            item_id=item_id,
            play_session_id=body.play_session_id,
            position_seconds=body.position_seconds,
            is_paused=body.is_paused,
        )
        return Response(status_code=204)
    except ExternalServiceError as e:
        logger.warning("Progress report failed for %s: %s", item_id, e)
        raise HTTPException(status_code=502, detail="Failed to report progress")


@router.post("/jellyfin/{item_id}/stop", status_code=204)
async def stop_jellyfin_playback(
    item_id: str,
    body: StopReportRequest = MsgSpecBody(StopReportRequest),
    playback_service: JellyfinPlaybackService = Depends(get_jellyfin_playback_service),
) -> Response:
    try:
        await playback_service.stop_playback(
            item_id=item_id,
            play_session_id=body.play_session_id,
            position_seconds=body.position_seconds,
        )
        return Response(status_code=204)
    except ExternalServiceError as e:
        logger.warning("Stop report failed for %s: %s", item_id, e)
        raise HTTPException(status_code=502, detail="Failed to report playback stop")


@router.head("/local/{track_id}")
async def head_local_file(
    track_id: int,
    local_service: LocalFilesService = Depends(get_local_files_service),
) -> Response:
    try:
        headers = await local_service.head_track(track_id)
        return Response(
            status_code=200,
            headers=headers,
            media_type=headers.get("Content-Type", "application/octet-stream"),
        )
    except ResourceNotFoundError:
        raise HTTPException(status_code=404, detail="Track file not found")
    except FileNotFoundError:
        raise HTTPException(status_code=404, detail="Track file not found on disk")
    except PermissionError:
        raise HTTPException(status_code=403, detail="Access denied: path is outside the music directory")
    except ExternalServiceError as e:
        logger.error("Local head error for track %s: %s", track_id, e)
        raise HTTPException(status_code=502, detail="Failed to check local file")
    except OSError as e:
        logger.error("OS error checking local track %s: %s", track_id, e)
        raise HTTPException(status_code=500, detail="Failed to read local file")


@router.get("/local/{track_id}")
async def stream_local_file(
    track_id: int,
    request: Request,
    local_service: LocalFilesService = Depends(get_local_files_service),
) -> StreamingResponse:
    try:
        range_header = request.headers.get("Range")
        chunks, headers, status_code = await local_service.stream_track(
            track_file_id=track_id,
            range_header=range_header,
        )
        return StreamingResponse(
            content=chunks,
            status_code=status_code,
            headers=headers,
            media_type=headers.get("Content-Type", "application/octet-stream"),
        )
    except ResourceNotFoundError:
        raise HTTPException(status_code=404, detail="Track file not found")
    except FileNotFoundError:
        raise HTTPException(status_code=404, detail="Track file not found on disk")
    except PermissionError:
        raise HTTPException(status_code=403, detail="Access denied: path is outside the music directory")
    except ExternalServiceError as e:
        detail = str(e)
        if "Range not satisfiable" in detail:
            raise HTTPException(status_code=416, detail="Range not satisfiable")
        logger.error("Local stream error for track %s: %s", track_id, e)
        raise HTTPException(status_code=502, detail="Failed to stream local file")
    except OSError as e:
        logger.error("OS error streaming local track %s: %s", track_id, e)
        raise HTTPException(status_code=500, detail="Failed to read local file")


@router.head("/navidrome/{item_id}")
async def head_navidrome_audio(
    item_id: str,
    playback_service: NavidromePlaybackService = Depends(get_navidrome_playback_service),
) -> Response:
    try:
        return await playback_service.proxy_head(item_id)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid stream request")
    except ExternalServiceError:
        raise HTTPException(status_code=502, detail="Failed to stream from Navidrome")


@router.get("/navidrome/{item_id}")
async def stream_navidrome_audio(
    item_id: str,
    request: Request,
    playback_service: NavidromePlaybackService = Depends(get_navidrome_playback_service),
) -> StreamingResponse:
    try:
        return await playback_service.proxy_stream(item_id, request.headers.get("Range"))
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid stream request")
    except ExternalServiceError as e:
        detail = str(e)
        if "416" in detail or "Range not satisfiable" in detail:
            raise HTTPException(status_code=416, detail="Range not satisfiable")
        raise HTTPException(status_code=502, detail="Failed to stream from Navidrome")


@router.post("/navidrome/{item_id}/scrobble")
async def scrobble_navidrome(
    item_id: str,
    playback_service: NavidromePlaybackService = Depends(get_navidrome_playback_service),
) -> dict[str, str]:
    ok = await playback_service.scrobble(item_id)
    return {"status": "ok" if ok else "error"}


@router.post("/navidrome/{item_id}/now-playing")
async def navidrome_now_playing(
    item_id: str,
    playback_service: NavidromePlaybackService = Depends(get_navidrome_playback_service),
) -> dict[str, str]:
    ok = await playback_service.report_now_playing(item_id)
    return {"status": "ok" if ok else "error"}


@router.post("/navidrome/{item_id}/stopped")
async def navidrome_stopped(
    item_id: str,
    playback_service: NavidromePlaybackService = Depends(get_navidrome_playback_service),
) -> dict[str, str]:
    await playback_service.clear_now_playing(item_id)
    return {"status": "ok"}


@router.head("/plex/{part_key:path}")
async def head_plex_audio(
    part_key: str,
    playback_service: PlexPlaybackService = Depends(get_plex_playback_service),
) -> Response:
    try:
        return await playback_service.proxy_head(part_key)
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid stream request")
    except ExternalServiceError:
        raise HTTPException(status_code=502, detail="Failed to stream from Plex")


@router.get("/plex/{part_key:path}")
async def stream_plex_audio(
    part_key: str,
    request: Request,
    playback_service: PlexPlaybackService = Depends(get_plex_playback_service),
) -> StreamingResponse:
    try:
        return await playback_service.proxy_stream(part_key, request.headers.get("Range"))
    except ValueError:
        raise HTTPException(status_code=400, detail="Invalid stream request")
    except ExternalServiceError as e:
        detail = str(e)
        if "416" in detail or "Range not satisfiable" in detail:
            raise HTTPException(status_code=416, detail="Range not satisfiable")
        raise HTTPException(status_code=502, detail="Failed to stream from Plex")


@router.post("/plex/{rating_key}/scrobble")
async def scrobble_plex(
    rating_key: str,
    playback_service: PlexPlaybackService = Depends(get_plex_playback_service),
) -> dict[str, str]:
    ok = await playback_service.scrobble(rating_key)
    return {"status": "ok" if ok else "error"}


@router.post("/plex/{rating_key}/now-playing")
async def plex_now_playing(
    rating_key: str,
    playback_service: PlexPlaybackService = Depends(get_plex_playback_service),
) -> dict[str, str]:
    ok = await playback_service.report_now_playing(rating_key)
    return {"status": "ok" if ok else "error"}


@router.post("/plex/{rating_key}/stopped")
async def plex_stopped(
    rating_key: str,
    playback_service: PlexPlaybackService = Depends(get_plex_playback_service),
) -> dict[str, str]:
    ok = await playback_service.report_stopped(rating_key)
    return {"status": "ok" if ok else "error"}
