"""StreamingService — resolves a track and opens a byte-range stream."""

import re
import uuid
from collections.abc import AsyncIterator
from dataclasses import dataclass

from app.domain.errors import NotFoundError, RangeNotSatisfiableError
from app.domain.ports import FileStorage, TrackRepository

_FORMAT_CONTENT_TYPE: dict[str, str] = {
    "mp3": "audio/mpeg",
    "flac": "audio/flac",
    "m4a": "audio/mp4",
    "aac": "audio/aac",
    "ogg": "audio/ogg",
    "opus": "audio/ogg",
    "wav": "audio/wav",
    "wma": "audio/x-ms-wma",
    "aiff": "audio/aiff",
    "aif": "audio/aiff",
}

_RANGE_RE = re.compile(r"bytes=(\d+)-(\d*)")


@dataclass
class StreamResult:
    stream: AsyncIterator[bytes]
    total_size: int
    content_length: int
    content_type: str
    start: int
    end: int
    is_partial: bool


def _parse_range(header: str | None, total_size: int) -> tuple[int, int | None, bool]:
    """Return (start, end, is_partial). Raises RangeNotSatisfiableError on invalid range."""
    if header is None:
        return 0, None, False

    m = _RANGE_RE.fullmatch(header.strip())
    if not m:
        return 0, None, False  # malformed → treat as absent per RFC 7233

    start = int(m.group(1))
    end: int | None = int(m.group(2)) if m.group(2) else None

    if start >= total_size:
        raise RangeNotSatisfiableError(total_size)

    if end is not None:
        if end >= total_size:
            end = total_size - 1
        if end < start:
            raise RangeNotSatisfiableError(total_size)

    return start, end, True


class StreamingService:
    def __init__(self, tracks: TrackRepository, storage: FileStorage) -> None:
        self._tracks = tracks
        self._storage = storage

    async def open_stream(
        self,
        track_id: uuid.UUID,
        range_header: str | None,
    ) -> StreamResult:
        track = await self._tracks.get_by_id(track_id)
        if track is None:
            raise NotFoundError("Track not found.")

        stat = await self._storage.stat(track.storage_uri)
        total_size = stat.size
        content_type = stat.content_type or _FORMAT_CONTENT_TYPE.get(
            track.file_format.lower(), "application/octet-stream"
        )

        start, end, is_partial = _parse_range(range_header, total_size)

        stream, _ = await self._storage.open_range(track.storage_uri, start, end)

        actual_end = end if end is not None else total_size - 1
        content_length = actual_end - start + 1

        return StreamResult(
            stream=stream,
            total_size=total_size,
            content_length=content_length,
            content_type=content_type,
            start=start,
            end=actual_end,
            is_partial=is_partial,
        )
