"""Threads API client implementation.

This is an initial Threads platform setup built around the Threads Graph API.
"""

from collections.abc import Awaitable, Callable
from datetime import UTC, datetime
from typing import Any, cast
from urllib.parse import urlparse

import httpx
from pydantic import HttpUrl

from marqetive.core.base import ProgressCallback, SocialMediaPlatform
from marqetive.core.exceptions import (
    PlatformAuthError,
    PlatformError,
    PostNotFoundError,
    ThreadCancelledException,
    ValidationError,
)
from marqetive.core.models import (
    AuthCredentials,
    Comment,
    MediaAttachment,
    MediaType,
    Post,
    PostCreateRequest,
    PostStatus,
    PostUpdateRequest,
    ProgressStatus,
)

THREADS_API_BASE = "https://graph.threads.net/v1.0"
THREADS_MAX_TEXT_LENGTH = 500


class ThreadsClient(SocialMediaPlatform):
    """Threads Graph API client.

    Current implementation focuses on platform setup and core post creation flow.
    Advanced features (comment CRUD, editing, media pre-upload) are intentionally
    left for follow-up iterations.
    """

    def __init__(
        self,
        credentials: AuthCredentials,
        timeout: float = 30.0,
        progress_callback: ProgressCallback | None = None,
    ) -> None:
        super().__init__(
            platform_name="threads",
            credentials=credentials,
            base_url=THREADS_API_BASE,
            timeout=timeout,
            progress_callback=progress_callback,
        )
        self.threads_user_id = self._resolve_threads_user_id()

    def _resolve_threads_user_id(self) -> str:
        """Resolve Threads user ID from credentials."""
        additional_user_id = self.credentials.additional_data.get("threads_user_id")

        if isinstance(additional_user_id, str) and additional_user_id:
            return additional_user_id
        if self.credentials.user_id:
            return self.credentials.user_id

        raise PlatformAuthError(
            "Threads requires user_id or 'threads_user_id' in additional_data",
            platform=self.platform_name,
        )

    async def authenticate(self) -> AuthCredentials:
        """Validate Threads credentials."""
        if await self.is_authenticated():
            return self.credentials

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

    async def refresh_token(self) -> AuthCredentials:
        """Refresh Threads access token.

        Token refresh is not implemented yet in this initial platform setup.
        """
        raise PlatformAuthError(
            "Threads token refresh is not implemented yet. Reconnect the account.",
            platform=self.platform_name,
            requires_reconnection=True,
        )

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

        try:
            await self.api_client.get(
                "/me",
                params={
                    "fields": "id,username",
                    "access_token": self.credentials.access_token,
                },
            )
            return True
        except httpx.HTTPError:
            return False

    def _validate_create_post_request(self, request: PostCreateRequest) -> None:
        """Validate Threads post creation request."""
        if not request.content and not request.media_urls and not request.media_ids:
            raise ValidationError(
                "Threads post requires content, media URL, or uploaded media ID",
                platform=self.platform_name,
                field="content",
            )

        if request.content and len(request.content) > THREADS_MAX_TEXT_LENGTH:
            raise ValidationError(
                f"Threads post exceeds {THREADS_MAX_TEXT_LENGTH} characters "
                f"({len(request.content)} provided)",
                platform=self.platform_name,
                field="content",
            )

        if len(request.media_urls) > 1 or len(request.media_ids) > 1:
            raise ValidationError(
                "Initial Threads setup supports one media item per post",
                platform=self.platform_name,
                field="media",
            )

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

        self._validate_create_post_request(request)

        payload: dict[str, Any] = {}
        if request.content:
            payload["text"] = request.content

        try:
            if request.media_ids:
                if request.content:
                    raise ValidationError(
                        "Threads media_ids represent prepared containers. "
                        "Text must be provided during media container creation.",
                        platform=self.platform_name,
                        field="content",
                    )
                creation_id = request.media_ids[0]
                container_data: dict[str, Any] = {"id": creation_id}
            else:
                if request.media_urls:
                    media_url = str(request.media_urls[0])
                    media_type = self._detect_media_type(media_url)
                    payload["media_type"] = media_type
                    if media_type == "IMAGE":
                        payload["image_url"] = media_url
                    else:
                        payload["video_url"] = media_url
                    if request.additional_data.get("alt_text"):
                        payload["alt_text"] = request.additional_data["alt_text"]
                else:
                    payload["media_type"] = "TEXT"

                container_response = await self.api_client.post(
                    f"/{self.threads_user_id}/threads",
                    data=payload,
                )
                creation_id = container_response.data.get("id")
                if not creation_id:
                    raise PlatformError(
                        "Threads API did not return a creation container id",
                        platform=self.platform_name,
                    )
                container_data = container_response.data

            publish_response = await self.api_client.post(
                f"/{self.threads_user_id}/threads_publish",
                data={"creation_id": creation_id},
            )
            post_id = publish_response.data.get("id")
            if not post_id:
                raise PlatformError(
                    "Threads API did not return a published post id",
                    platform=self.platform_name,
                )

            return Post(
                post_id=post_id,
                platform=self.platform_name,
                content=request.content,
                status=PostStatus.PUBLISHED,
                created_at=datetime.now(UTC),
                raw_data={
                    "container": container_data,
                    "publish": publish_response.data,
                },
            )

        except httpx.HTTPStatusError as e:
            raise PlatformError(
                f"Failed to create Threads post: {e.response.text}",
                platform=self.platform_name,
                status_code=e.response.status_code,
            ) from e
        except httpx.HTTPError as e:
            raise PlatformError(
                f"Network error creating Threads post: {e}",
                platform=self.platform_name,
            ) from e

    async def create_thread(
        self,
        posts: list[PostCreateRequest],
        cancellation_check: Callable[[], Awaitable[bool]] | None = None,
    ) -> list[Post]:
        """Create a Threads thread (multiple linked posts).

        Posts are published sequentially, where each post replies to the
        previously created post.
        """
        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,
                )

            request_update: dict[str, Any] = {}
            additional_data = dict(post_request.additional_data)
            if reply_to_id is not None:
                additional_data["reply_to_post_id"] = reply_to_id
            request_update["additional_data"] = additional_data

            final_request = post_request.model_copy(update=request_update)
            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_post.post_id,
            )

        return created_posts

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

        try:
            response = await self.api_client.get(
                f"/{post_id}",
                params={
                    "fields": (
                        "id,text,permalink,timestamp,media_type,username,"
                        "like_count,reply_count,repost_count"
                    ),
                    "access_token": self.credentials.access_token,
                },
            )

            data = response.data
            created_at = self._parse_timestamp(data.get("timestamp"))
            repost_count = int(data.get("repost_count", 0) or 0)

            return Post(
                post_id=str(data.get("id", post_id)),
                platform=self.platform_name,
                content=data.get("text"),
                status=PostStatus.PUBLISHED,
                url=data.get("permalink"),
                created_at=created_at,
                author_id=data.get("username") or self.credentials.user_id,
                likes_count=int(data.get("like_count", 0) or 0),
                comments_count=int(data.get("reply_count", 0) or 0),
                shares_count=repost_count,
                raw_data=data,
            )

        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                raise PostNotFoundError(
                    post_id=post_id,
                    platform=self.platform_name,
                    status_code=404,
                ) from e
            raise PlatformError(
                f"Failed to retrieve Threads post: {e.response.text}",
                platform=self.platform_name,
                status_code=e.response.status_code,
            ) from e
        except httpx.HTTPError as e:
            raise PlatformError(
                f"Network error retrieving Threads post: {e}",
                platform=self.platform_name,
            ) from e

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

        Editing is not supported in this initial Threads setup.
        """
        del post_id, request
        raise PlatformError(
            "Threads post editing is not supported in this client yet",
            platform=self.platform_name,
        )

    async def delete_post(self, post_id: str) -> bool:
        """Delete a Threads post/media object."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        try:
            response = await self.api_client.delete(
                f"{THREADS_API_BASE}/{post_id}",
                params={"access_token": self.credentials.access_token},
            )
            data = response.data
            if isinstance(data, dict) and "success" in data:
                return bool(data["success"])
            return True
        except httpx.HTTPStatusError as e:
            if e.response.status_code == 404:
                raise PostNotFoundError(
                    post_id=post_id,
                    platform=self.platform_name,
                    status_code=404,
                ) from e
            raise PlatformError(
                f"Failed to delete Threads post: {e.response.text}",
                platform=self.platform_name,
                status_code=e.response.status_code,
            ) from e
        except httpx.HTTPError as e:
            raise PlatformError(
                f"Network error deleting Threads post: {e}",
                platform=self.platform_name,
            ) from e

    async def get_comments(
        self,
        post_id: str,
        limit: int = 50,
        offset: int = 0,
    ) -> list[Comment]:
        """Retrieve comments for a Threads post.

        Comment retrieval is not implemented in this initial setup.
        """
        del post_id, limit, offset
        raise PlatformError(
            "Threads comment retrieval is not supported in this client yet",
            platform=self.platform_name,
        )

    async def create_comment(self, post_id: str, content: str) -> Comment:
        """Create a comment on a Threads post.

        Comment creation is not implemented in this initial setup.
        """
        del post_id, content
        raise PlatformError(
            "Threads comment creation is not supported in this client yet",
            platform=self.platform_name,
        )

    async def delete_comment(self, comment_id: str) -> bool:
        """Delete a Threads comment.

        Comment deletion is not implemented in this initial setup.
        """
        del comment_id
        raise PlatformError(
            "Threads comment deletion is not supported in this client yet",
            platform=self.platform_name,
        )

    async def upload_media(
        self,
        media_url: str,
        media_type: str,
        alt_text: str | None = None,
    ) -> MediaAttachment:
        """Create a Threads media container from a public media URL."""
        if not self.api_client:
            raise RuntimeError("Client must be used as async context manager")

        normalized_type = media_type.strip().lower()
        if normalized_type in {"image", "photo"}:
            threads_media_type = "IMAGE"
            model_media_type = MediaType.IMAGE
            media_field = "image_url"
        elif normalized_type in {"video", "reel"}:
            threads_media_type = "VIDEO"
            model_media_type = MediaType.VIDEO
            media_field = "video_url"
        else:
            raise ValidationError(
                "Threads upload_media supports only image or video media types",
                platform=self.platform_name,
                field="media_type",
            )

        payload: dict[str, Any] = {
            "media_type": threads_media_type,
            media_field: media_url,
        }
        if alt_text:
            payload["alt_text"] = alt_text

        try:
            response = await self.api_client.post(
                f"/{self.threads_user_id}/threads",
                data=payload,
            )
        except httpx.HTTPStatusError as e:
            raise PlatformError(
                f"Failed to upload Threads media: {e.response.text}",
                platform=self.platform_name,
                status_code=e.response.status_code,
            ) from e
        except httpx.HTTPError as e:
            raise PlatformError(
                f"Network error uploading Threads media: {e}",
                platform=self.platform_name,
            ) from e

        container_id = response.data.get("id")
        if not container_id:
            raise PlatformError(
                "Threads API did not return a media container id",
                platform=self.platform_name,
            )

        return MediaAttachment(
            media_id=container_id,
            media_type=model_media_type,
            url=cast(HttpUrl, media_url),
            alt_text=alt_text,
        )

    @staticmethod
    def _detect_media_type(media_url: str) -> str:
        """Detect Threads media type from URL extension."""
        parsed_path = urlparse(media_url).path.lower()
        video_exts = (".mp4", ".mov", ".m4v", ".webm")
        if parsed_path.endswith(video_exts):
            return "VIDEO"
        return "IMAGE"

    @staticmethod
    def _parse_timestamp(raw_timestamp: Any) -> datetime:
        """Parse timestamp from Threads responses."""
        if isinstance(raw_timestamp, str):
            try:
                return datetime.fromisoformat(raw_timestamp.replace("Z", "+00:00"))
            except ValueError:
                pass
        return datetime.now(UTC)
