"""Twitter thread creation and management.

This module provides utilities for creating and managing Twitter threads
(sequences of connected tweets).
"""

from dataclasses import dataclass
from typing import Any

import tweepy

from src.marqetive.platforms.exceptions import PlatformError, ValidationError
from src.marqetive.platforms.models import Post, PostStatus


@dataclass
class TweetData:
    """Data for a single tweet in a thread.

    Attributes:
        content: Tweet text content (max 280 characters).
        media_ids: List of Twitter media IDs to attach.
        alt_texts: Optional alt texts for media (same order as media_ids).
    """

    content: str
    media_ids: list[str] | None = None
    alt_texts: list[str] | None = None

    def __post_init__(self) -> None:
        """Validate tweet data after initialization."""
        # Validate content length
        if not self.content:
            raise ValidationError(
                "Tweet content cannot be empty",
                platform="twitter",
                field="content",
            )

        if len(self.content) > 280:
            raise ValidationError(
                f"Tweet exceeds 280 characters ({len(self.content)} characters)",
                platform="twitter",
                field="content",
            )

        # Validate media count (Twitter allows max 4 images or 1 video per tweet)
        if self.media_ids and len(self.media_ids) > 4:
            raise ValidationError(
                f"Too many media attachments ({len(self.media_ids)}). "
                "Twitter allows maximum 4 images or 1 video per tweet.",
                platform="twitter",
                field="media_ids",
            )

        # Validate alt texts match media count
        if self.alt_texts:
            if not self.media_ids:
                raise ValidationError(
                    "Cannot provide alt_texts without media_ids",
                    platform="twitter",
                    field="alt_texts",
                )
            if len(self.alt_texts) != len(self.media_ids):
                raise ValidationError(
                    f"Number of alt_texts ({len(self.alt_texts)}) must match "
                    f"number of media_ids ({len(self.media_ids)})",
                    platform="twitter",
                    field="alt_texts",
                )


@dataclass
class ThreadResult:
    """Result of creating a Twitter thread.

    Attributes:
        thread_id: ID of the first tweet in the thread.
        tweet_ids: List of all tweet IDs in order.
        tweets: List of Post objects for each tweet.
        total_tweets: Total number of tweets in the thread.
    """

    thread_id: str
    tweet_ids: list[str]
    tweets: list[Post]

    @property
    def total_tweets(self) -> int:
        """Get total number of tweets in thread."""
        return len(self.tweet_ids)

    def __str__(self) -> str:
        """String representation of thread result."""
        return (
            f"Thread(id={self.thread_id}, "
            f"tweets={self.total_tweets}, "
            f"ids={self.tweet_ids})"
        )


class TwitterThreadManager:
    """Manager for creating and managing Twitter threads.

    Example:
        >>> manager = TwitterThreadManager(tweepy_client)
        >>> tweets = [
        ...     TweetData("First tweet in thread"),
        ...     TweetData("Second tweet with more info"),
        ...     TweetData("Final tweet with conclusion"),
        ... ]
        >>> result = await manager.create_thread(tweets)
        >>> print(f"Created thread: {result.thread_id}")
    """

    def __init__(self, tweepy_client: tweepy.Client) -> None:
        """Initialize thread manager.

        Args:
            tweepy_client: Authenticated tweepy Client instance.
        """
        self.client = tweepy_client

    async def create_thread(
        self,
        tweets: list[TweetData],
        *,
        auto_number: bool = False,
        number_format: str = "{index}/{total}",
    ) -> ThreadResult:
        """Create a Twitter thread from multiple tweets.

        Args:
            tweets: List of TweetData objects representing the thread.
            auto_number: If True, automatically add tweet numbers to content.
            number_format: Format string for auto-numbering (supports {index}, {total}).

        Returns:
            ThreadResult with thread ID and all tweet information.

        Raises:
            ValidationError: If thread data is invalid.
            PlatformError: If thread creation fails.

        Example:
            >>> tweets = [
            ...     TweetData("Part 1: Introduction"),
            ...     TweetData("Part 2: Details"),
            ...     TweetData("Part 3: Conclusion"),
            ... ]
            >>> result = await manager.create_thread(tweets, auto_number=True)
        """
        if not tweets:
            raise ValidationError(
                "Thread must contain at least one tweet",
                platform="twitter",
                field="tweets",
            )

        if len(tweets) > 25:
            raise ValidationError(
                f"Thread too long ({len(tweets)} tweets). "
                "Consider splitting into multiple threads.",
                platform="twitter",
                field="tweets",
            )

        thread_id: str | None = None
        tweet_ids: list[str] = []
        post_objects: list[Post] = []
        previous_tweet_id: str | None = None

        try:
            for index, tweet_data in enumerate(tweets, start=1):
                # Prepare tweet content
                content = tweet_data.content

                # Add auto-numbering if requested
                if auto_number and len(tweets) > 1:
                    number_prefix = number_format.format(
                        index=index,
                        total=len(tweets),
                    )
                    content = f"{number_prefix} {content}"

                    # Validate length after numbering
                    if len(content) > 280:
                        raise ValidationError(
                            f"Tweet {index} exceeds 280 characters after auto-numbering "
                            f"({len(content)} characters). Consider shorter content or "
                            "disable auto_number.",
                            platform="twitter",
                            field="content",
                        )

                # Prepare tweet parameters
                tweet_params: dict[str, Any] = {"text": content}

                # Add reply-to for threading
                if previous_tweet_id:
                    tweet_params["in_reply_to_tweet_id"] = previous_tweet_id

                # Add media if provided
                if tweet_data.media_ids:
                    tweet_params["media_ids"] = tweet_data.media_ids

                # Create tweet
                response = self.client.create_tweet(**tweet_params)
                tweet_id = str(response.data["id"])  # type: ignore[index]

                # Store IDs
                if thread_id is None:
                    thread_id = tweet_id
                tweet_ids.append(tweet_id)
                previous_tweet_id = tweet_id

                # Fetch full tweet details
                tweet_response = self.client.get_tweet(
                    tweet_id,
                    tweet_fields=[
                        "created_at",
                        "public_metrics",
                        "attachments",
                        "author_id",
                    ],
                )

                # Convert to Post object
                tweet = tweet_response.data  # type: ignore[attr-defined]
                post = Post(
                    post_id=tweet_id,
                    platform="twitter",
                    content=content,
                    status=PostStatus.PUBLISHED,
                    created_at=tweet.created_at,
                    author_id=str(tweet.author_id) if tweet.author_id else None,
                    raw_data=tweet.data,
                )
                post_objects.append(post)

        except tweepy.TweepyException as e:
            # If thread creation fails partway through, we have partial thread
            # Log the created tweets for cleanup if needed
            if tweet_ids:
                raise PlatformError(
                    f"Thread creation failed at tweet {len(tweet_ids) + 1}. "
                    f"Partial thread created with IDs: {tweet_ids}. "
                    f"Error: {e}",
                    platform="twitter",
                ) from e
            raise PlatformError(
                f"Failed to create thread: {e}",
                platform="twitter",
            ) from e

        if not thread_id:
            raise PlatformError(
                "Thread creation completed but no thread ID available",
                platform="twitter",
            )

        return ThreadResult(
            thread_id=thread_id,
            tweet_ids=tweet_ids,
            tweets=post_objects,
        )

    async def delete_thread(
        self,
        thread_id: str,
        tweet_ids: list[str],
        *,
        continue_on_error: bool = True,
    ) -> dict[str, bool]:
        """Delete all tweets in a thread.

        Args:
            thread_id: ID of the first tweet (for logging).
            tweet_ids: List of all tweet IDs to delete.
            continue_on_error: If True, continue deleting even if some fail.

        Returns:
            Dictionary mapping tweet_id to deletion success status.

        Example:
            >>> result = await manager.delete_thread(
            ...     thread_id="123",
            ...     tweet_ids=["123", "456", "789"]
            ... )
            >>> print(f"Deleted: {sum(result.values())} / {len(result)} tweets")
        """
        results: dict[str, bool] = {}
        errors: list[str] = []

        # Delete in reverse order (newest to oldest)
        for tweet_id in reversed(tweet_ids):
            try:
                self.client.delete_tweet(tweet_id)
                results[tweet_id] = True
            except tweepy.TweepyException as e:
                results[tweet_id] = False
                errors.append(f"Tweet {tweet_id}: {e}")

                if not continue_on_error:
                    raise PlatformError(
                        f"Failed to delete tweet {tweet_id} in thread {thread_id}: {e}",
                        platform="twitter",
                    ) from e

        # If there were errors but continue_on_error=True, log them
        if errors:
            error_summary = "; ".join(errors)
            raise PlatformError(
                f"Some tweets in thread {thread_id} failed to delete: {error_summary}",
                platform="twitter",
            )

        return results


def split_content_into_tweets(
    content: str,
    *,
    max_length: int = 280,
    split_on_sentences: bool = True,
    add_continuation: bool = True,
    continuation_suffix: str = "...",
) -> list[str]:
    """Split long content into multiple tweet-sized chunks.

    Attempts to split on sentence boundaries when possible.

    Args:
        content: Long text content to split.
        max_length: Maximum characters per tweet (default: 280).
        split_on_sentences: Try to split on sentence boundaries.
        add_continuation: Add continuation indicator to split tweets.
        continuation_suffix: Suffix to add when splitting mid-content.

    Returns:
        List of tweet content strings.

    Example:
        >>> long_text = "This is a very long piece of content..." * 20
        >>> tweets = split_content_into_tweets(long_text)
        >>> print(f"Split into {len(tweets)} tweets")
    """
    if len(content) <= max_length:
        return [content]

    tweets: list[str] = []
    remaining = content

    while remaining:
        # Calculate available space
        available = max_length
        if add_continuation and len(remaining) > max_length:
            available -= len(continuation_suffix)

        if len(remaining) <= available:
            # Last chunk
            tweets.append(remaining)
            break

        # Find split point
        split_point = available

        if split_on_sentences:
            # Try to split on sentence boundary
            sentence_ends = [". ", "! ", "? ", ".\n", "!\n", "?\n"]
            best_split = 0

            for sent_end in sentence_ends:
                pos = remaining[:available].rfind(sent_end)
                if pos > best_split:
                    best_split = pos + len(sent_end)

            if best_split > 0:
                split_point = best_split
            else:
                # Try to split on word boundary
                last_space = remaining[:available].rfind(" ")
                if last_space > 0:
                    split_point = last_space + 1

        # Extract chunk
        chunk = remaining[:split_point].rstrip()

        # Add continuation indicator if not at end
        if add_continuation and len(remaining) > split_point:
            chunk += continuation_suffix

        tweets.append(chunk)
        remaining = remaining[split_point:].lstrip()

    return tweets


def create_numbered_thread(
    content_parts: list[str],
    *,
    number_format: str = "{index}/{total}",
    media_per_tweet: list[list[str]] | None = None,
) -> list[TweetData]:
    """Create a numbered thread from content parts.

    Convenience function to create TweetData with auto-numbering.

    Args:
        content_parts: List of content strings, one per tweet.
        number_format: Format string for numbering.
        media_per_tweet: Optional list of media ID lists, one per tweet.

    Returns:
        List of TweetData objects ready for thread creation.

    Example:
        >>> parts = ["Introduction", "Main content", "Conclusion"]
        >>> tweets = create_numbered_thread(parts)
        >>> # Tweets will be: "1/3 Introduction", "2/3 Main content", "3/3 Conclusion"
    """
    total = len(content_parts)
    tweet_data_list: list[TweetData] = []

    for index, content in enumerate(content_parts, start=1):
        # Add number prefix
        number_prefix = number_format.format(index=index, total=total)
        numbered_content = f"{number_prefix} {content}"

        # Get media for this tweet if provided
        media_ids = None
        if media_per_tweet and index - 1 < len(media_per_tweet):
            media_ids = media_per_tweet[index - 1]

        tweet_data_list.append(
            TweetData(
                content=numbered_content,
                media_ids=media_ids,
            )
        )

    return tweet_data_list
