from __future__ import annotations

from pathlib import Path
from typing import Any
from urllib.parse import quote, unquote


class LocalPrivateStorage:
    scheme = "private-media"

    def __init__(self, root: str | Path) -> None:
        self.root = Path(root)
        self.root.mkdir(parents=True, exist_ok=True)

    def put_media(
        self,
        *,
        user_id: str,
        pet_id: str,
        media_id: str,
        filename: str,
        content_type: str,
        content: bytes,
    ) -> str:
        safe_name = _safe_filename(filename, content_type)
        media_dir = self.root / _safe_segment(user_id) / _safe_segment(pet_id) / _safe_segment(media_id)
        media_dir.mkdir(parents=True, exist_ok=True)
        path = media_dir / safe_name
        path.write_bytes(content)
        return f"{self.scheme}://{quote(user_id)}/{quote(pet_id)}/{quote(media_id)}/{quote(safe_name)}"

    def read(self, storage_ref: str) -> bytes:
        return self._path_from_ref(storage_ref).read_bytes()

    def delete(self, storage_ref: str) -> None:
        path = self._path_from_ref(storage_ref)
        if path.exists():
            path.unlink()

    def exists(self, storage_ref: str) -> bool:
        try:
            return self._path_from_ref(storage_ref).exists()
        except ValueError:
            return False

    def exists_for_media(self, media_id: str) -> bool:
        matches = list(self.root.glob(f"*/*/{_safe_segment(media_id)}/*"))
        return any(path.is_file() for path in matches)

    def health(self) -> dict[str, Any]:
        return {
            "ok": self.root.exists() and self.root.is_dir(),
            "root_configured": True,
        }

    def _path_from_ref(self, storage_ref: str) -> Path:
        prefix = f"{self.scheme}://"
        if not storage_ref.startswith(prefix):
            raise ValueError("unsupported storage ref")
        parts = [unquote(part) for part in storage_ref[len(prefix) :].split("/") if part]
        if len(parts) != 4:
            raise ValueError("invalid storage ref")
        safe_parts = [_safe_segment(part) for part in parts[:3]]
        safe_name = _safe_filename(parts[3], "")
        path = self.root.joinpath(*safe_parts, safe_name).resolve()
        root = self.root.resolve()
        try:
            path.relative_to(root)
        except ValueError as exc:
            raise ValueError("storage ref escapes storage root") from exc
        return path


def _safe_segment(value: str) -> str:
    cleaned = "".join(ch for ch in str(value) if ch.isalnum() or ch in {"_", "-"}).strip("._-")
    return cleaned or "item"


def _safe_filename(filename: str, content_type: str) -> str:
    suffix = Path(filename).suffix.lower()
    if suffix not in {".png", ".jpg", ".jpeg", ".webp", ".gif"}:
        suffix = {
            "image/png": ".png",
            "image/jpeg": ".jpg",
            "image/webp": ".webp",
            "image/gif": ".gif",
        }.get(content_type, ".bin")
    stem = _safe_segment(Path(filename).stem or "upload")
    return f"{stem}{suffix}"
