"""BlueSky media upload manager for image blobs."""

import contextlib
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

import httpx

from marqetive.core.exceptions import MediaUploadError
from marqetive.platforms.bluesky.exceptions import map_bluesky_error
from marqetive.utils.file_handlers import download_file, read_file_bytes
from marqetive.utils.retry import retry_async_func

BLUESKY_API_BASE = "https://bsky.social/xrpc"
BLUESKY_ALLOWED_IMAGE_EXTENSIONS = frozenset({".jpg", ".jpeg", ".png", ".webp", ".gif"})


def resolve_bluesky_image_mime_type(
    media_url: str,
    *,
    platform: str = "bluesky",
) -> str:
    """Resolve image MIME type based on URL/path extension."""
    parsed = urlparse(media_url)
    extension = Path(parsed.path).suffix.lower()
    if extension not in BLUESKY_ALLOWED_IMAGE_EXTENSIONS:
        raise MediaUploadError(
            f"Unsupported BlueSky image format '{extension}'. "
            f"Allowed: {', '.join(sorted(BLUESKY_ALLOWED_IMAGE_EXTENSIONS))}",
            platform=platform,
        )

    return {
        ".jpg": "image/jpeg",
        ".jpeg": "image/jpeg",
        ".png": "image/png",
        ".webp": "image/webp",
        ".gif": "image/gif",
    }[extension]


@dataclass
class BlueSkyMediaUploadResult:
    """Result for a BlueSky media upload."""

    media_id: str
    size_bytes: int
    blob: dict[str, Any]


class BlueSkyMediaManager:
    """Manager for BlueSky media uploads."""

    def __init__(
        self,
        *,
        access_token: str,
        token_type: str = "Bearer",
        timeout: float = 30.0,
    ) -> None:
        self.access_token = access_token
        self.token_type = token_type
        self.timeout = timeout
        self._client = httpx.AsyncClient(timeout=httpx.Timeout(timeout))

    async def __aenter__(self) -> "BlueSkyMediaManager":
        return self

    async def __aexit__(self, *args: object) -> None:
        await self._client.aclose()

    def update_credentials(
        self, *, access_token: str, token_type: str | None = None
    ) -> None:
        """Update auth credentials used for subsequent uploads."""
        self.access_token = access_token
        if token_type is not None:
            self.token_type = token_type

    def get_image_mime_type(self, media_url: str) -> str:
        """Resolve image MIME type based on URL/path extension."""
        return resolve_bluesky_image_mime_type(media_url, platform="bluesky")

    async def upload_image(self, media_url: str) -> BlueSkyMediaUploadResult:
        """Upload an image to BlueSky and return blob metadata."""
        mime_type = self.get_image_mime_type(media_url)
        media_path = media_url
        temp_path: str | None = None

        try:
            try:
                if media_url.startswith(("http://", "https://")):
                    media_path = await download_file(
                        media_url,
                        timeout=self.timeout,
                    )
                    temp_path = media_path
                elif not Path(media_url).exists():
                    raise FileNotFoundError(f"Media file not found: {media_url}")

                content = await read_file_bytes(media_path)
            except httpx.HTTPError as e:
                raise MediaUploadError(
                    f"Failed to download media from URL: {e}",
                    platform="bluesky",
                ) from e
            except OSError as e:
                raise MediaUploadError(
                    f"Failed to read media file: {e}",
                    platform="bluesky",
                ) from e

            headers = {
                "Authorization": f"{self.token_type} {self.access_token}",
                "Content-Type": mime_type,
            }

            try:

                async def _upload_blob() -> dict[str, Any]:
                    response = await self._client.post(
                        f"{BLUESKY_API_BASE}/com.atproto.repo.uploadBlob",
                        content=content,
                        headers=headers,
                    )
                    response.raise_for_status()
                    return response.json()

                data = await retry_async_func(_upload_blob)
            except httpx.HTTPStatusError as e:
                response_data: dict
                try:
                    response_data = e.response.json()
                except Exception:
                    response_data = {}
                raise map_bluesky_error(
                    e.response.status_code,
                    response_data=response_data,
                    error_message=e.response.text,
                ) from e
            except httpx.HTTPError as e:
                raise MediaUploadError(
                    f"Failed to upload media to BlueSky: {e}",
                    platform="bluesky",
                ) from e

            blob = data.get("blob")
            if not blob:
                raise MediaUploadError(
                    "BlueSky uploadBlob response missing blob field",
                    platform="bluesky",
                )

            blob_ref = blob.get("ref", {})
            media_id = blob_ref.get("$link") or blob.get("cid") or "unknown_blob"

            return BlueSkyMediaUploadResult(
                media_id=str(media_id),
                size_bytes=len(content),
                blob=blob,
            )
        finally:
            if temp_path:
                with contextlib.suppress(OSError):
                    Path(temp_path).unlink()
