"""BlueSky API client implementation using XRPC endpoints."""

import asyncio
import contextlib
from collections.abc import Awaitable, Callable
from datetime import UTC, datetime, timedelta
from pathlib import Path
from typing import Any
from urllib.parse import urlparse

import httpx
from pydantic import HttpUrl

from marqetive.core.base import ProgressCallback, SocialMediaPlatform
from marqetive.core.exceptions import (
    MediaUploadError,
    PlatformAuthError,
    PlatformError,
    PostNotFoundError,
    ThreadCancelledException,
    ValidationError,
)
from marqetive.core.models import (
    AuthCredentials,
    Comment,
    CommentStatus,
    MediaAttachment,
    MediaType,
    Post,
    PostCreateRequest,
    PostStatus,
    PostUpdateRequest,
    ProgressStatus,
)
from marqetive.platforms.bluesky.exceptions import map_bluesky_error
from marqetive.platforms.bluesky.media import (
    BLUESKY_ALLOWED_IMAGE_EXTENSIONS,
    BLUESKY_ALLOWED_VIDEO_EXTENSIONS,
    BlueSkyMediaManager,
    resolve_bluesky_image_mime_type,
)
from marqetive.utils.retry import retry_async_func

BLUESKY_API_BASE = "https://bsky.social/xrpc"
BLUESKY_POST_COLLECTION = "app.bsky.feed.post"
BLUESKY_REPLY_REFERENCE_MAX_ATTEMPTS = 3
BLUESKY_REPLY_REFERENCE_BASE_DELAY_SECONDS = 0.25


class BlueSkyClient(SocialMediaPlatform):
    """BlueSky API client using direct XRPC calls."""

    def __init__(
        self,
        credentials: AuthCredentials,
        timeout: float = 30.0,
        progress_callback: ProgressCallback | None = None,
    ) -> None:
        super().__init__(
            platform_name="bluesky",
            credentials=credentials,
            base_url=BLUESKY_API_BASE,
            timeout=timeout,
            progress_callback=progress_callback,
        )
        self.repo_identifier = self._get_repo_identifier()
        self._media_manager: BlueSkyMediaManager | None = None
        self._pds_base_url: str | None = None

    async def __aenter__(self) -> "BlueSkyClient":
        await super().__aenter__()
        self._media_manager = BlueSkyMediaManager(
            access_token=self.credentials.access_token,
            token_type=self.credentials.token_type,
            timeout=self.timeout,
            pds_base_url=self.base_url,
        )
        await self._media_manager.__aenter__()
        return self

    async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
        if self._media_manager:
            await self._media_manager.__aexit__(exc_type, exc_val, exc_tb)
            self._media_manager = None
        await super().__aexit__(exc_type, exc_val, exc_tb)

    def _get_repo_identifier(self) -> str:
        """Resolve BlueSky repo identifier (DID or handle)."""
        did = self.credentials.additional_data.get("did")
        if isinstance(did, str) and did:
            return did
        if self.credentials.user_id:
            return self.credentials.user_id
        if self.credentials.username:
            return self.credentials.username

        raise PlatformAuthError(
            "BlueSky requires user_id (DID/handle), username, or 'did' in additional_data",
            platform=self.platform_name,
        )

    async def authenticate(self) -> AuthCredentials:
        """Validate current BlueSky credentials."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")
        if await self.is_authenticated():
            return self.credentials

        raise PlatformAuthError(
            "Invalid or expired credentials. Please re-authenticate via BlueSky.",
            platform=self.platform_name,
        )

    async def refresh_token(self) -> AuthCredentials:
        """Refresh BlueSky access token using refresh JWT."""
        if not self.credentials.refresh_token:
            raise PlatformAuthError(
                "No refresh token available",
                platform=self.platform_name,
            )

        try:

            async def _refresh_session() -> dict[str, Any]:
                async with httpx.AsyncClient(timeout=self.timeout) as client:
                    response = await client.post(
                        f"{BLUESKY_API_BASE}/com.atproto.server.refreshSession",
                        headers={
                            "Authorization": f"Bearer {self.credentials.refresh_token}"
                        },
                    )
                    response.raise_for_status()
                    return response.json()

            data = await retry_async_func(_refresh_session)

        except httpx.HTTPStatusError as e:
            response_data: dict[str, Any]
            try:
                response_data = e.response.json()
            except Exception:
                response_data = {}
            mapped = map_bluesky_error(
                e.response.status_code,
                response_data=response_data,
                error_message=e.response.text,
            )
            if isinstance(mapped, Exception):
                raise mapped from e
            raise PlatformAuthError(
                f"Failed to refresh BlueSky token: {e.response.text}",
                platform=self.platform_name,
                status_code=e.response.status_code,
            ) from e

        except httpx.HTTPError as e:
            raise PlatformAuthError(
                f"Network error refreshing token: {e}",
                platform=self.platform_name,
            ) from e

        self.credentials.access_token = data["accessJwt"]
        if "refreshJwt" in data:
            self.credentials.refresh_token = data["refreshJwt"]
        if "did" in data:
            self.credentials.user_id = data["did"]
            self.repo_identifier = data["did"]
            self.credentials.additional_data["did"] = data["did"]
        if "handle" in data:
            self.credentials.username = data["handle"]
        expires_at_raw = data.get("expiresAt")
        expires_in = data.get("expiresIn")

        if isinstance(expires_at_raw, str):
            with contextlib.suppress(ValueError):
                self.credentials.expires_at = datetime.fromisoformat(
                    expires_at_raw.replace("Z", "+00:00")
                )
        elif isinstance(expires_at_raw, int | float):
            ts = float(expires_at_raw)
            if ts > 1_000_000_000_000:
                ts /= 1000.0
            self.credentials.expires_at = datetime.fromtimestamp(ts, tz=UTC)
        elif isinstance(expires_in, int | float):
            self.credentials.expires_at = datetime.now(UTC) + timedelta(
                seconds=float(expires_in)
            )
        else:
            self.credentials.expires_at = datetime.now(UTC) + timedelta(
                hours=1, minutes=50
            )

        if self._media_manager:
            self._media_manager.update_credentials(
                access_token=self.credentials.access_token,
                token_type=self.credentials.token_type,
            )

        return self.credentials

    async def is_authenticated(self) -> bool:
        """Check whether the current access token is valid."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        try:
            response = await retry_async_func(
                self.api_client.get,
                "/com.atproto.server.getSession",
            )
            return bool(response.data.get("did"))
        except (httpx.HTTPError, TypeError):
            return False

    def _validate_create_post_request(self, request: PostCreateRequest) -> None:
        """Validate BlueSky post creation request."""
        pass

    def _get_image_mime_type(self, media_url: str) -> str:
        """Get MIME type for an image URL after extension validation."""
        return resolve_bluesky_image_mime_type(
            media_url,
            platform=self.platform_name,
        )

    def _classify_media_urls(
        self, media_urls: list[str]
    ) -> tuple[list[str], list[str]]:
        """Split media URLs into image and video groups."""
        image_urls: list[str] = []
        video_urls: list[str] = []

        for media_url in media_urls:
            parsed = urlparse(media_url)
            extension = Path(parsed.path).suffix.lower()
            if extension in BLUESKY_ALLOWED_IMAGE_EXTENSIONS:
                image_urls.append(media_url)
            elif extension in BLUESKY_ALLOWED_VIDEO_EXTENSIONS:
                video_urls.append(media_url)
            else:
                allowed_images = ", ".join(sorted(BLUESKY_ALLOWED_IMAGE_EXTENSIONS))
                allowed_videos = ", ".join(sorted(BLUESKY_ALLOWED_VIDEO_EXTENSIONS))
                raise MediaUploadError(
                    "Unsupported BlueSky media format "
                    f"'{extension}'. Allowed images: {allowed_images}. "
                    f"Allowed videos: {allowed_videos}",
                    platform=self.platform_name,
                )

        if image_urls and video_urls:
            raise MediaUploadError(
                "BlueSky does not support mixing images and videos in a single post",
                platform=self.platform_name,
            )

        if len(video_urls) > 1:
            raise MediaUploadError(
                "BlueSky supports only one video per post",
                platform=self.platform_name,
            )

        return image_urls, video_urls

    def _get_video_alt_text(self, request: PostCreateRequest) -> str:
        """Resolve alt text for a BlueSky video embed."""
        alt_texts = request.additional_data.get("alt_texts")
        if isinstance(alt_texts, list) and alt_texts and isinstance(alt_texts[0], str):
            return alt_texts[0]

        alt_text = request.additional_data.get("alt_text")
        return alt_text if isinstance(alt_text, str) else ""

    def _get_video_aspect_ratio(
        self, request: PostCreateRequest
    ) -> dict[str, int] | None:
        """Resolve aspect ratio for a BlueSky video embed, if provided."""
        aspect_ratio = request.additional_data.get("aspect_ratio")
        if not isinstance(aspect_ratio, dict):
            return None

        width = aspect_ratio.get("width")
        height = aspect_ratio.get("height")
        if (
            isinstance(width, int)
            and isinstance(height, int)
            and width > 0
            and height > 0
        ):
            return {"width": width, "height": height}

        return None

    def _resolve_video_upload_did(self) -> str:
        """Resolve DID required for BlueSky video uploads."""
        did_value = self.repo_identifier
        if not did_value.startswith("did:"):
            did_candidate = self.credentials.additional_data.get("did")
            if isinstance(did_candidate, str) and did_candidate.startswith("did:"):
                did_value = did_candidate
            elif isinstance(
                self.credentials.user_id, str
            ) and self.credentials.user_id.startswith("did:"):
                did_value = self.credentials.user_id
            else:
                raise MediaUploadError(
                    "BlueSky video uploads require a DID. "
                    "Provide credentials.additional_data['did'] or user_id as a DID.",
                    platform=self.platform_name,
                )

        return did_value

    async def _resolve_pds_base_url(self) -> str:
        """Resolve the user's PDS base URL for service auth."""
        if self._pds_base_url and self._pds_base_url != self.base_url:
            return self._pds_base_url
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        for key in ("pds", "pds_url", "pds_endpoint"):
            candidate = self.credentials.additional_data.get(key)
            if isinstance(candidate, str) and candidate:
                pds_url = candidate.rstrip("/")
                if not pds_url.endswith("/xrpc"):
                    pds_url = f"{pds_url}/xrpc"
                self._pds_base_url = pds_url
                if self._media_manager:
                    self._media_manager.update_pds_base_url(pds_url)
                return pds_url

        pds_url: str | None = None
        response = await retry_async_func(
            self.api_client.get,
            "/com.atproto.server.getSession",
        )
        did_doc = response.data.get("didDoc", {})
        services = did_doc.get("service", []) if isinstance(did_doc, dict) else []
        for service in services:
            if not isinstance(service, dict):
                continue
            service_id = str(service.get("id", ""))
            service_type = str(service.get("type", ""))
            if (
                service_id.endswith("#atproto_pds")
                or service_type == "AtprotoPersonalDataServer"
            ):
                endpoint = service.get("serviceEndpoint")
                if isinstance(endpoint, str) and endpoint:
                    pds_url = endpoint.rstrip("/")
                    break

        if not pds_url:
            did_value = self._resolve_video_upload_did()
            if did_value.startswith("did:web:"):
                host = did_value.removeprefix("did:web:").strip()
                if host:
                    pds_url = f"https://{host}"

            elif did_value.startswith("did:plc:"):
                try:
                    async with httpx.AsyncClient(timeout=self.timeout) as client:
                        plc_response = await client.get(
                            f"https://plc.directory/{did_value}"
                        )
                        plc_response.raise_for_status()
                        plc_doc = plc_response.json()

                except httpx.HTTPError:
                    plc_doc = {}
                    raise

                plc_services = plc_doc.get("service", [])
                if isinstance(plc_services, dict):
                    plc_services = [
                        {"id": key, **value}
                        for key, value in plc_services.items()
                        if isinstance(value, dict)
                    ]
                if isinstance(plc_services, list):
                    for service in plc_services:
                        if not isinstance(service, dict):
                            continue
                        service_id = str(service.get("id", ""))
                        service_type = str(service.get("type", ""))
                        if (
                            service_id.endswith("#atproto_pds")
                            or service_type == "AtprotoPersonalDataServer"
                        ):
                            endpoint = service.get("serviceEndpoint") or service.get(
                                "endpoint"
                            )
                            if isinstance(endpoint, str) and endpoint:
                                pds_url = endpoint.rstrip("/")
                                break

        if not pds_url:
            pds_url = self.base_url.rstrip("/")

        if not pds_url.endswith("/xrpc"):
            pds_url = f"{pds_url}/xrpc"

        self._pds_base_url = pds_url
        self.credentials.additional_data["pds"] = pds_url
        if self._media_manager:
            self._media_manager.update_pds_base_url(pds_url)
        return pds_url

    async def _upload_video_for_embed(self, media_url: str) -> dict[str, Any]:
        """Upload a video via the video service and return the processed blob."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")
        if not self._media_manager:
            raise RuntimeError("Client must be used as async context manager")
        upload_name = ""
        if media_url.startswith(("http://", "https://")):
            parsed_url = urlparse(media_url)
            upload_name = Path(parsed_url.path).name
        else:
            upload_name = Path(media_url).name

        await self._resolve_pds_base_url()
        result = await self._media_manager.upload_video(
            media_url,
            did=self._resolve_video_upload_did(),
            upload_name=upload_name or None,
        )
        return result.blob

    def _normalize_post_uri(self, post_id: str) -> str:
        """Normalize post identifier to an AT URI."""
        if post_id.startswith("at://"):
            return post_id

        if post_id.startswith("https://bsky.app/profile/"):
            parts = post_id.replace("https://bsky.app/profile/", "").split("/")
            if len(parts) >= 3 and parts[1] == "post":
                profile = parts[0]
                rkey = parts[2]
                return f"at://{profile}/{BLUESKY_POST_COLLECTION}/{rkey}"

        return f"at://{self.repo_identifier}/{BLUESKY_POST_COLLECTION}/{post_id}"

    def _extract_rkey_from_uri(self, uri: str) -> str:
        """Extract record key from an AT URI."""
        parts = uri.split("/")
        if len(parts) < 5:
            raise ValidationError(
                f"Invalid BlueSky post URI: {uri}",
                platform=self.platform_name,
                field="post_id",
            )
        return parts[-1]

    def _build_post_url(self, uri: str) -> HttpUrl | None:
        """Convert AT URI to public bsky.app URL."""
        parts = uri.split("/")
        if len(parts) < 5:
            return None
        profile = parts[2]
        rkey = parts[4]
        return HttpUrl(f"https://bsky.app/profile/{profile}/post/{rkey}")

    async def _resolve_reply_reference(
        self, post_uri: str
    ) -> dict[str, dict[str, str]]:
        """Resolve reply root/parent reference for replying to a post."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        post: dict[str, Any] | None = None
        for attempt in range(BLUESKY_REPLY_REFERENCE_MAX_ATTEMPTS):
            response = await retry_async_func(
                self.api_client.get,
                "/app.bsky.feed.getPosts",
                params={"uris": post_uri},
            )
            posts = response.data.get("posts", [])
            if posts:
                post = posts[0]
                break

            if attempt < BLUESKY_REPLY_REFERENCE_MAX_ATTEMPTS - 1:
                delay_seconds = BLUESKY_REPLY_REFERENCE_BASE_DELAY_SECONDS * (
                    attempt + 1
                )
                await asyncio.sleep(delay_seconds)

        if not post:
            raise PostNotFoundError(post_uri, self.platform_name)

        parent_uri = post.get("uri")
        parent_cid = post.get("cid")
        if not parent_uri or not parent_cid:
            raise PlatformError(
                "BlueSky reply target is missing uri/cid",
                platform=self.platform_name,
            )

        root = post.get("record", {}).get("reply", {}).get("root", {})
        root_uri = root.get("uri", parent_uri)
        root_cid = root.get("cid", parent_cid)

        return {
            "root": {"uri": root_uri, "cid": root_cid},
            "parent": {"uri": parent_uri, "cid": parent_cid},
        }

    async def create_post(self, request: PostCreateRequest) -> Post:
        """Create and publish a BlueSky post."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")
        if not self._media_manager:
            raise RuntimeError("Client must be used as async context manager")

        self._validate_create_post_request(request)

        langs: list[str] | None = None
        if (
            isinstance(request.additional_data.get("langs"), list)
            and request.additional_data["langs"]
        ):
            langs = [
                str(lang)
                for lang in request.additional_data["langs"]
                if isinstance(lang, str)
            ]

        reply_to_post_id = request.reply_to_post_id or request.additional_data.get(
            "reply_to_uri"
        )
        record: dict[str, Any] = {
            "$type": BLUESKY_POST_COLLECTION,
            "text": request.content,
            "createdAt": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
        }
        if langs:
            record["langs"] = langs

        if reply_to_post_id:
            reply_uri = self._normalize_post_uri(str(reply_to_post_id))
            record["reply"] = await self._resolve_reply_reference(reply_uri)

        if request.media_urls:
            image_urls, video_urls = self._classify_media_urls(request.media_urls)

            if video_urls:
                alt_text = self._get_video_alt_text(request)
                aspect_ratio = self._get_video_aspect_ratio(request)
                blob = await self._upload_video_for_embed(video_urls[0])
                embed: dict[str, Any] = {
                    "$type": "app.bsky.embed.video",
                    "video": blob,
                }
                if alt_text:
                    embed["alt"] = alt_text
                if aspect_ratio:
                    embed["aspectRatio"] = aspect_ratio
                record["embed"] = embed
            else:
                alt_texts = request.additional_data.get("alt_texts", [])
                images: list[dict[str, Any]] = []
                for idx, media_url in enumerate(image_urls):
                    self._get_image_mime_type(media_url)
                    alt_text = (
                        alt_texts[idx]
                        if isinstance(alt_texts, list)
                        and idx < len(alt_texts)
                        and isinstance(alt_texts[idx], str)
                        else ""
                    )
                    upload_result = await self._media_manager.upload_image(media_url)
                    images.append({"alt": alt_text, "image": upload_result.blob})
                record["embed"] = {"$type": "app.bsky.embed.images", "images": images}

        payload = {
            "repo": self.repo_identifier,
            "collection": BLUESKY_POST_COLLECTION,
            "record": record,
        }

        try:
            response = await retry_async_func(
                self.api_client.post,
                "/com.atproto.repo.createRecord",
                data=payload,
            )
        except httpx.HTTPStatusError as e:
            response_data: dict[str, Any]
            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 PlatformError(
                f"Failed to create BlueSky post: {e}",
                platform=self.platform_name,
            ) from e

        uri = response.data["uri"]

        return Post(
            post_id=uri,
            platform=self.platform_name,
            content=request.content,
            status=PostStatus.PUBLISHED,
            created_at=datetime.now(),
            author_id=self.repo_identifier,
            url=self._build_post_url(uri),
            raw_data=response.data,
        )

    async def create_thread(
        self,
        posts: list[PostCreateRequest],
        cancellation_check: Callable[[], Awaitable[bool]] | None = None,
    ) -> list[Post]:
        """Create a BlueSky thread by chaining replies to the thread head."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        if not posts:
            raise ValidationError(
                "At least one post is required for thread creation",
                platform=self.platform_name,
            )

        created_posts: list[Post] = []
        reply_to_id: str | None = None

        for idx, post_request in enumerate(posts):
            if cancellation_check is not None and await cancellation_check():
                raise ThreadCancelledException(
                    f"Thread cancelled after {len(created_posts)} of {len(posts)} posts",
                    platform=self.platform_name,
                    posted_tweets=created_posts,
                )

            self._validate_create_post_request(post_request)

            final_request = post_request
            if reply_to_id is not None:
                final_request = post_request.model_copy(
                    update={"reply_to_post_id": reply_to_id}
                )

            created_post = await self.create_post(final_request)
            created_posts.append(created_post)
            reply_to_id = created_post.post_id

            await self._emit_progress(
                operation="create_thread",
                status=ProgressStatus.PROCESSING,
                progress=idx + 1,
                total=len(posts),
                message=f"Post {idx + 1}/{len(posts)} created",
                entity_id=created_posts[-1].post_id,
            )

        return created_posts

    def _parse_post(self, post_data: dict[str, Any]) -> Post:
        """Parse BlueSky feed post payload to Post model."""
        record = post_data.get("record", {})
        created_at_raw = record.get("createdAt")

        created_at = datetime.now()
        if isinstance(created_at_raw, str):
            try:
                created_at = datetime.fromisoformat(
                    created_at_raw.replace("Z", "+00:00")
                )
            except ValueError:
                created_at = datetime.now()

        uri = post_data.get("uri", "")

        return Post(
            post_id=uri,
            platform=self.platform_name,
            content=record.get("text"),
            status=PostStatus.PUBLISHED,
            url=self._build_post_url(uri),
            created_at=created_at,
            author_id=post_data.get("author", {}).get("did"),
            likes_count=int(post_data.get("likeCount", 0) or 0),
            comments_count=int(post_data.get("replyCount", 0) or 0),
            shares_count=int(post_data.get("repostCount", 0) or 0),
            views_count=0,  # BlueSky API doesn't expose view counts
            raw_data=post_data,
        )

    async def _get_post_via_xrpc(self, post_uri: str) -> Post:
        """Retrieve a post through direct XRPC."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        try:
            response = await retry_async_func(
                self.api_client.get,
                "/app.bsky.feed.getPosts",
                params={"uris": post_uri},
            )
            posts = response.data.get("posts", [])
            if posts and isinstance(posts[0], dict):
                return self._parse_post(posts[0])
            raise PostNotFoundError(post_uri, self.platform_name, status_code=404)
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                raise PostNotFoundError(
                    post_uri, self.platform_name, status_code=404
                ) from e
            response_data: dict[str, Any]
            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 PlatformError(
                f"Failed to fetch BlueSky post via XRPC: {e}",
                platform=self.platform_name,
            ) from e

    async def _delete_post_via_xrpc(self, post_uri: str) -> bool:
        """Delete a post through XRPC."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        parts = post_uri.split("/")
        if len(parts) < 5:
            raise ValidationError(
                f"Invalid BlueSky post URI: {post_uri}",
                platform=self.platform_name,
                field="post_id",
            )

        repo = parts[2]
        collection = parts[3]
        rkey = parts[4]
        try:
            await retry_async_func(
                self.api_client.post,
                "/com.atproto.repo.deleteRecord",
                data={
                    "repo": repo,
                    "collection": collection,
                    "rkey": rkey,
                },
            )
            return True
        except httpx.HTTPStatusError as e:
            response_data: dict[str, Any]
            try:
                response_data = e.response.json()
            except Exception:
                response_data = {}
            mapped = map_bluesky_error(
                e.response.status_code,
                response_data=response_data,
                error_message=e.response.text,
            )
            if isinstance(mapped, Exception):
                raise mapped from e
            raise PlatformError(
                f"Failed to delete BlueSky post via XRPC: {e.response.text}",
                platform=self.platform_name,
                status_code=e.response.status_code,
            ) from e
        except httpx.HTTPError as e:
            raise PlatformError(
                f"Failed to delete BlueSky post via XRPC: {e}",
                platform=self.platform_name,
            ) from e
        except Exception as e:
            raise PlatformError(
                f"Failed to delete BlueSky post via XRPC: {e}",
                platform=self.platform_name,
            ) from e

    async def get_post(self, post_id: str) -> Post:
        """Retrieve a BlueSky post by URI or record key."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        post_uri = self._normalize_post_uri(post_id)
        return await self._get_post_via_xrpc(post_uri)

    async def delete_post(self, post_id: str) -> bool:
        """Delete a BlueSky post by URI or record key."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        post_uri = self._normalize_post_uri(post_id)
        return await self._delete_post_via_xrpc(post_uri)

    async def update_post(
        self,
        post_id: str,  # noqa: ARG002
        request: PostUpdateRequest,  # noqa: ARG002
    ) -> Post:
        """Update a BlueSky post.

        Note: BlueSky does not provide an edit flow in this client implementation.
        """
        raise PlatformError(
            "BlueSky post editing is not supported by this client.",
            platform=self.platform_name,
        )

    def _parse_comment(self, post_data: dict[str, Any], root_post_id: str) -> Comment:
        """Parse BlueSky reply post into Comment model."""
        record = post_data.get("record", {})
        created_at_raw = record.get("createdAt")

        created_at = datetime.now()
        if isinstance(created_at_raw, str):
            try:
                created_at = datetime.fromisoformat(
                    created_at_raw.replace("Z", "+00:00")
                )
            except ValueError:
                created_at = datetime.now()

        return Comment(
            comment_id=post_data.get("uri", ""),
            post_id=root_post_id,
            platform=self.platform_name,
            content=record.get("text", ""),
            author_id=post_data.get("author", {}).get("did", ""),
            author_username=post_data.get("author", {}).get("handle"),
            created_at=created_at,
            likes_count=int(post_data.get("likeCount", 0) or 0),
            replies_count=int(post_data.get("replyCount", 0) or 0),
            status=CommentStatus.VISIBLE,
            raw_data=post_data,
        )

    async def get_comments(
        self,
        post_id: str,
        limit: int = 50,
        offset: int = 0,
    ) -> list[Comment]:
        """Retrieve comments (replies) for a BlueSky post."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        post_uri = self._normalize_post_uri(post_id)

        try:
            response = await retry_async_func(
                self.api_client.get,
                "/app.bsky.feed.getPostThread",
                params={"uri": post_uri, "depth": 2},
            )
            thread = response.data.get("thread", {})
            replies = thread.get("replies", []) if isinstance(thread, dict) else []

            comments: list[Comment] = []
            for reply in replies:
                if not isinstance(reply, dict):
                    continue
                reply_post = reply.get("post")
                if isinstance(reply_post, dict):
                    comments.append(self._parse_comment(reply_post, post_uri))

            return comments[offset : offset + limit]
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                raise PostNotFoundError(
                    post_id, self.platform_name, status_code=404
                ) from e
            response_data: dict[str, Any]
            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 PlatformError(
                f"Failed to fetch BlueSky comments: {e}",
                platform=self.platform_name,
            ) from e

    async def create_comment(self, post_id: str, content: str) -> Comment:
        """Create a reply comment on a BlueSky post."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        post_uri = self._normalize_post_uri(post_id)
        reply_ref = await self._resolve_reply_reference(post_uri)

        payload = {
            "repo": self.repo_identifier,
            "collection": BLUESKY_POST_COLLECTION,
            "record": {
                "$type": BLUESKY_POST_COLLECTION,
                "text": content,
                "createdAt": datetime.now(UTC).isoformat().replace("+00:00", "Z"),
                "reply": reply_ref,
            },
        }

        try:
            response = await retry_async_func(
                self.api_client.post,
                "/com.atproto.repo.createRecord",
                data=payload,
            )
        except httpx.HTTPStatusError as e:
            response_data: dict[str, Any]
            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 PlatformError(
                f"Failed to create BlueSky comment: {e}",
                platform=self.platform_name,
            ) from e

        return Comment(
            comment_id=response.data["uri"],
            post_id=post_uri,
            platform=self.platform_name,
            content=content,
            author_id=self.repo_identifier,
            created_at=datetime.now(),
            status=CommentStatus.VISIBLE,
            raw_data=response.data,
        )

    async def delete_comment(self, comment_id: str) -> bool:
        """Delete a BlueSky comment (reply post)."""
        return await self.delete_post(comment_id)

    async def upload_media(
        self,
        media_url: str,
        media_type: str,
        alt_text: str | None = None,
    ) -> MediaAttachment:
        """Upload media to BlueSky and return blob metadata."""
        if not self._media_manager:
            raise RuntimeError("Client must be used as async context manager")

        media_type_lower = media_type.lower()
        if media_type_lower not in {"image", "video"}:
            raise MediaUploadError(
                "BlueSkyClient supports only image or video uploads",
                platform=self.platform_name,
            )

        try:
            if media_type_lower == "image":
                result = await self._media_manager.upload_image(media_url)
            else:
                await self._resolve_pds_base_url()
                result = await self._media_manager.upload_video(
                    media_url,
                    did=self._resolve_video_upload_did(),
                )
        except MediaUploadError:
            raise
        except Exception as e:
            raise MediaUploadError(
                f"Failed to upload media to BlueSky: {e}",
                platform=self.platform_name,
            ) from e

        return MediaAttachment(
            media_id=result.media_id,
            media_type=(
                MediaType.IMAGE if media_type_lower == "image" else MediaType.VIDEO
            ),
            url=HttpUrl(media_url),
            size_bytes=result.size_bytes,
            alt_text=alt_text,
        )
