"""Fediverse client service for fetching hashtag timelines."""

import asyncio
from datetime import datetime
from typing import Any

import httpx
from minimal_activitypub.client_2_server import ActivityPub

from fenliu.config import settings
from fenliu.custom_types import MediaAttachmentDict
from fenliu.logging import get_logger

logger = get_logger(__name__)


class FediverseClient:
    """Client for interacting with Fediverse instances."""

    def __init__(self, instance: str | None = None) -> None:
        """Initialize Fediverse client.

        Args:
            instance: Fediverse instance domain (e.g., "mastodon.social").
                      Uses default from settings if not provided.

        """
        self.instance = instance or settings.default_instance
        logger.debug(f"FediverseClient.__init__: instance={self.instance}")

        httpx_client = httpx.AsyncClient(timeout=settings.api_timeout)
        self.client = ActivityPub(instance=self.instance, client=httpx_client)
        self._rate_limit_delay = settings.rate_limit_delay
        self._initialized = False
        logger.debug(f"FediverseClient.__init__: client initialized with timeout={settings.api_timeout}")

    async def _ensure_initialized(self) -> None:
        """Ensure the ActivityPub client is initialized."""
        if not self._initialized:
            logger.debug(f"FediverseClient._ensure_initialized: determining instance type for {self.instance}")
            await self.client.determine_instance_type()
            self._initialized = True
            logger.debug("FediverseClient._ensure_initialized: instance type determined")

    async def close(self) -> None:
        """Close the HTTP client."""
        logger.debug("FediverseClient.close: closing HTTP client")
        await self.client.client.aclose()
        logger.debug("FediverseClient.close: HTTP client closed")

    async def fetch_hashtag_timeline(
        self,
        hashtag: str,
        limit: int = 20,
        min_id: str | None = None,
        max_id: str | None = None,
    ) -> list[dict[str, Any]]:
        """Fetch posts for a specific hashtag.

        Args:
            hashtag: Hashtag to fetch (without #).
            limit: Maximum number of posts to fetch.
            min_id: Return results newer than this ID.
            max_id: Return results older than this ID.

        Returns:
            List of post dictionaries.

        """
        try:
            # Ensure client is initialized
            await self._ensure_initialized()

            # Clean hashtag (remove # if present)
            hashtag = hashtag.lstrip("#").strip()
            logger.debug(f"FediverseClient.fetch_hashtag_timeline: cleaned hashtag={hashtag}")
            if not hashtag:
                logger.warning("Empty hashtag provided")
                return []

            logger.info(f"Fetching #{hashtag} from {self.instance} (limit: {limit})")
            logger.debug(f"FediverseClient.fetch_hashtag_timeline: min_id={min_id}, max_id={max_id}")

            # Apply rate limiting delay
            if self._rate_limit_delay > 0:
                logger.debug(
                    f"FediverseClient.fetch_hashtag_timeline: applying rate limit delay={self._rate_limit_delay}s"
                )
                await asyncio.sleep(self._rate_limit_delay)

            # Fetch posts from Fediverse
            posts = await self.client.get_hashtag_timeline(
                hashtag=hashtag,
                limit=limit,
                min_id=min_id,
                max_id=max_id,
            )

            logger.info(f"Fetched {len(posts)} posts for #{hashtag} from {self.instance}")
            return posts

        except Exception as e:
            logger.error(f"Error fetching #{hashtag} from {self.instance}: {e}")
            return []

    def _parse_created_at(self, created_at: Any) -> datetime | None:
        """Parse created_at timestamp from Fediverse post.

        Args:
            created_at: Raw created_at value from API.

        Returns:
            Parsed datetime or None if parsing fails.

        """
        if isinstance(created_at, str):
            try:
                return datetime.fromisoformat(created_at.replace("Z", "+00:00"))
            except (ValueError, AttributeError):
                return None
        return None

    def _extract_author_info(self, post: dict[str, Any]) -> dict[str, Any]:
        """Extract author information from Fediverse post.

        Args:
            post: Raw post data from Fediverse API.

        Returns:
            Dictionary with author information.

        """
        account = post.get("account", {})
        return {
            "author_username": account.get("username", ""),
            "author_display_name": account.get("display_name", ""),
            "author_url": account.get("url", ""),
            "author_is_bot": account.get("bot", False),
        }

    def _extract_hashtags(self, post: dict[str, Any]) -> list[str]:
        """Extract hashtags from Fediverse post.

        Args:
            post: Raw post data from Fediverse API.

        Returns:
            List of hashtags.

        """
        hashtags = []
        tags = post.get("tags", [])
        for tag in tags:
            if isinstance(tag, dict) and tag.get("name"):
                hashtags.append(tag["name"])
        return hashtags

    def _extract_media_attachments(self, post: dict[str, Any]) -> list[MediaAttachmentDict]:
        """Extract media attachments from Fediverse post.

        Args:
            post: Raw post data from Fediverse API.

        Returns:
            List of media attachment dictionaries.

        """
        media_attachments = []
        raw_media = post.get("media_attachments", [])

        for media in raw_media:
            if not isinstance(media, dict):
                continue

            media_type = media.get("type", "unknown")
            media_url = media.get("url") or media.get("remote_url") or media.get("preview_url")

            if not media_url:
                continue

            media_description = media.get("description") or media.get("alt_text") or ""

            media_attachments.append(
                {
                    "type": media_type,
                    "url": media_url,
                    "description": media_description,
                    "preview_url": media.get("preview_url") or media_url,
                }
            )

        return media_attachments

    def _build_post_data_dict(  # noqa: PLR0913
        self,
        post_id: str,
        content: str,
        created_at: datetime | None,
        author_info: dict[str, Any],
        hashtags: list[str],
        boosts: int,
        likes: int,
        replies: int,
        url: str,
        media_attachments: list[MediaAttachmentDict],
        raw_post: dict[str, Any],
    ) -> dict[str, Any]:
        """Build post data dictionary from extracted components.

        Note: This function has many parameters because it consolidates all extracted
        post data from the Fediverse API into a single dictionary format.
        This is intentional and necessary for data transformation.


        Args:
            post_id: Post identifier.
            content: Post content.
            created_at: Post creation timestamp.
            author_info: Dictionary with author information.
            hashtags: List of hashtags.
            boosts: Number of boosts/reblogs.
            likes: Number of likes/favorites.
            replies: Number of replies.
            url: Post URL.
            media_attachments: List of media attachments.
            raw_post: Raw post data from API.

        Returns:
            Dictionary with extracted post data.

        """
        return {
            "post_id": post_id,
            "content": content,
            "author_username": author_info["author_username"],
            "author_display_name": author_info["author_display_name"],
            "author_url": author_info["author_url"],
            "author_is_bot": author_info["author_is_bot"],
            "hashtags": hashtags,
            "boosts": boosts,
            "likes": likes,
            "replies": replies,
            "url": url,
            "created_at": created_at,
            "media_attachments": media_attachments,
            "raw_data": raw_post,  # Keep raw data for debugging
        }

    def extract_post_data(self, post: dict[str, Any]) -> dict[str, Any]:
        """Extract relevant data from a Fediverse post.

        Args:
            post: Raw post data from Fediverse API.

        Returns:
            Dictionary with extracted post data.

        """
        try:
            # Extract basic information
            post_id = post.get("id", "")
            content = post.get("content", "")
            created_at = self._parse_created_at(post.get("created_at"))

            # Extract author information
            author_info = self._extract_author_info(post)

            # Extract hashtags
            hashtags = self._extract_hashtags(post)

            # Extract engagement metrics
            boosts = post.get("reblogs_count", 0)
            likes = post.get("favourites_count", 0)
            replies = post.get("replies_count", 0)

            # Extract URL
            url = post.get("url", "")

            # Extract media attachments
            media_attachments = self._extract_media_attachments(post)

            return self._build_post_data_dict(
                post_id=post_id,
                content=content,
                created_at=created_at,
                author_info=author_info,
                hashtags=hashtags,
                boosts=boosts,
                likes=likes,
                replies=replies,
                url=url,
                media_attachments=media_attachments,
                raw_post=post,
            )

        except Exception as e:
            logger.error(f"Error extracting post data: {e}")
            return {}

    async def fetch_and_process_hashtag(
        self,
        hashtag: str,
        limit: int = 20,
    ) -> list[dict[str, Any]]:
        """Fetch and process posts for a hashtag.

        Args:
            hashtag: Hashtag to fetch (without #).
            limit: Maximum number of posts to fetch.

        Returns:
            List of processed post data.

        """
        raw_posts = await self.fetch_hashtag_timeline(hashtag, limit)
        processed_posts = []

        for raw_post in raw_posts:
            post_data = self.extract_post_data(raw_post)
            if post_data:  # Only include successfully processed posts
                processed_posts.append(post_data)

        logger.info(f"Processed {len(processed_posts)} posts for #{hashtag}")
        return processed_posts


async def test_fediverse_connection() -> bool:
    """Test connection to default Fediverse instance.

    Returns:
        True if connection successful, False otherwise.

    """
    client = None
    try:
        client = FediverseClient()
        # Try to fetch a common hashtag
        posts = await client.fetch_hashtag_timeline("test", limit=1)
        return len(posts) >= 0  # Even empty response means API is reachable
    except Exception as e:
        logger.error(f"Fediverse connection test failed: {e}")
        return False
    finally:
        if client:
            await client.close()
