"""스토리지 백엔드 추상 인터페이스 — plan ds-s1-backend-1 D-1.

ABC 는 첫날부터 Range(offset/length) stream 계약을 포함한다 — D-2 의 원격 streaming
Range pass-through 가 afterthought 가 아니라 인터페이스 의무가 되도록.

is_local=True 백엔드는 로컬 파일시스템 경로를 노출 → 호출부가 Starlette FileResponse
(Range 자동 처리)를 그대로 쓴다. 원격 백엔드는 stream()/stat() 로 Range 를 구현한다.
"""

from __future__ import annotations

import os
from abc import ABC, abstractmethod
from collections.abc import AsyncIterator
from dataclasses import dataclass


class StorageNotConfigured(RuntimeError):
    """활성화되지 않은(미프로비전) 백엔드 호출 — 503 으로 표면화. silent fallback 금지."""


@dataclass
class StatResult:
    exists: bool
    size: int


class StorageBackend(ABC):
    """원본 파일 접근 추상 인터페이스."""

    # 로컬 파일시스템 경로를 노출하는가 (FileResponse 직결 가능 여부).
    is_local: bool = False

    @abstractmethod
    def local_path(self, rel_path: str) -> os.PathLike[str] | None:
        """is_local=True 면 물리 경로 반환(FileResponse 용). 원격 백엔드는 None."""

    @abstractmethod
    async def stat(self, rel_path: str) -> StatResult:
        """크기/존재 여부. 미구성 백엔드는 StorageNotConfigured raise."""

    @abstractmethod
    def stream(
        self, rel_path: str, *, start: int | None = None, end: int | None = None
    ) -> AsyncIterator[bytes]:
        """[start, end] 바이트 범위(inclusive)를 async 청크로 yield (Range pass-through).

        start/end 가 None 이면 전체. 미구성 백엔드는 StorageNotConfigured raise.
        """
        raise NotImplementedError
