"""Twitter-specific CRDT document parser.

This module provides parsing for CRDT documents containing Twitter
(X) post data with twittercard nodes.
"""

from __future__ import annotations

import json
import re
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

from marqetive.utils.crdt.exceptions import CRDTEmptyDocumentError, CRDTParseError
from marqetive.utils.crdt.models import (
    AutoNumberConfig,
    ParsedTwitterDocument,
    TwitterCardContent,
)
from marqetive.utils.crdt.platforms.base import BaseCRDTParser

if TYPE_CHECKING:
    from pycrdt import Doc, XmlElement

# Twitter platform limits
TWITTER_MAX_CHARACTERS = 280
TWITTER_MAX_MEDIA_IMAGES = 4

# Pattern to extract tweet ID from OG URLs (twitter.com or x.com)
QUOTED_TWEET_PATTERN = re.compile(r"(?:twitter\.com|x\.com)/[^/]+/status/(\d+)")


class TwitterCRDTParser(BaseCRDTParser[ParsedTwitterDocument]):
    """Parser for Twitter CRDT documents.

    Handles parsing of twittercard nodes which represent individual tweets
    in a thread. Supports media attachments and quoted tweet extraction.

    Validation rules:
        - Max 280 characters per card
        - Max 4 media attachments per card
        - Threads supported (multiple cards)
    """

    @property
    def platform_name(self) -> str:
        """Return the platform name."""
        return "twitter"

    @property
    def root_node_type(self) -> str:
        """Return the root node type for Twitter."""
        return "twittercard"

    def parse_document(
        self, doc: Doc, fragment_name: str = "default"
    ) -> ParsedTwitterDocument:
        """Parse a Twitter CRDT document.

        Args:
            doc: The pycrdt Doc object to parse
            fragment_name: Name of the XML fragment to parse

        Returns:
            ParsedTwitterDocument with parsed cards

        Raises:
            CRDTEmptyDocumentError: If no cards found
            CRDTParseError: If parsing fails
        """
        fragment = self._get_fragment(doc, fragment_name)
        raw_attributes = self._get_node_attributes(fragment)

        # Parse auto-number config from twitterthread node if present
        auto_number_config: AutoNumberConfig | None = None
        thread_nodes = self._find_nodes_by_type(fragment, "twitterthread")
        if thread_nodes:
            auto_number_config = self._parse_auto_number_config(thread_nodes[0])

        # Find all twittercard nodes
        card_nodes = self._find_nodes_by_type(fragment, self.root_node_type)

        if not card_nodes:
            raise CRDTEmptyDocumentError(
                "No Twitter cards found in document",
                fragment_name=fragment_name,
            )

        # Parse each card
        cards: list[TwitterCardContent] = []
        for i, node in enumerate(card_nodes):
            try:
                card = self._parse_card(node, i)
                cards.append(card)
            except Exception as e:
                raise CRDTParseError(
                    f"Failed to parse Twitter card at position {i}: {e}",
                    platform=self.platform_name,
                    node_type=self.root_node_type,
                    position=i,
                ) from e

        return ParsedTwitterDocument(
            cards=cards,
            auto_number_config=auto_number_config,
            raw_attributes=raw_attributes,
            parsed_at=datetime.now(UTC),
        )

    def validate_schema(self, doc: Doc, fragment_name: str = "default") -> list[str]:
        """Validate a Twitter CRDT document.

        Args:
            doc: The pycrdt Doc object to validate
            fragment_name: Name of the XML fragment to validate

        Returns:
            List of validation errors (empty if valid)
        """
        errors: list[str] = []

        try:
            result = self.parse_document(doc, fragment_name)
        except CRDTEmptyDocumentError as e:
            return [str(e)]
        except CRDTParseError as e:
            return [str(e)]

        for i, card in enumerate(result.cards):
            card_errors = self._validate_card(card, i)
            errors.extend(card_errors)

        return errors

    def _parse_card(self, node: XmlElement, index: int) -> TwitterCardContent:
        """Parse a single twittercard node.

        Matches production twitter_parser.py behavior for attribute names
        and data formats.

        Args:
            node: The twittercard XML node
            index: Position of the card in the thread

        Returns:
            TwitterCardContent with parsed data
        """
        attrs = self._get_node_attributes(node)

        # Extract text content
        text = self._extract_text(node)

        # Parse media via space-separated URL strings
        media = self._parse_media_urls(node, "media")

        # Parse isQuoted with string conversion (production lines 97-100)
        is_quoted = False
        is_quoted_value = attrs.get("isQuoted")
        if is_quoted_value is not None:
            is_quoted = str(is_quoted_value).lower() == "true"

        # Parse quotedCardId with int validation (production lines 103-111)
        quoted_card_id: str | None = None
        quoted_card_id_value = attrs.get("quotedCardId")
        if quoted_card_id_value is not None:
            try:
                quoted_card_id = str(int(quoted_card_id_value))
            except (ValueError, TypeError):
                quoted_card_id = None

        # Extract quoted tweet ID from og URL when quoteOg is true (production lines 80-90)
        quoted_tweet_id: str | None = None
        quote_og_value = attrs.get("quoteOg")
        if quote_og_value is not None and str(quote_og_value).lower() == "true":
            og_value = attrs.get("og")
            if og_value:
                quoted_tweet_id = self._extract_quoted_tweet_id(og_value)

        # Read autoNumber from card attrs
        auto_number = attrs.get("autoNumber")
        if auto_number is not None:
            auto_number = str(auto_number)

        # Get card ID if available
        card_id = attrs.get("id") or attrs.get("cardId") or f"card_{index}"

        return TwitterCardContent(
            id=card_id,
            text=text,
            media=media,
            is_quoted=is_quoted,
            quoted_card_id=quoted_card_id,
            quoted_tweet_id=quoted_tweet_id,
            auto_number=auto_number,
        )

    def _extract_quoted_tweet_id(self, url: str | dict[str, Any]) -> str | None:
        """Extract tweet ID from an OG URL.

        Handles both string URLs and dict with "url" key.
        Matches production twitter_parser.py:60-69.

        Args:
            url: OG URL string or dict containing url key

        Returns:
            Tweet ID string or None
        """
        if isinstance(url, dict):
            url = url.get("url", "")
        if not isinstance(url, str):
            return None
        match = QUOTED_TWEET_PATTERN.search(url)
        return match.group(1) if match else None

    def _parse_auto_number_config(self, node: XmlElement) -> AutoNumberConfig | None:
        """Parse auto-number configuration from a twitterthread node.

        Args:
            node: The twitterthread XML element

        Returns:
            AutoNumberConfig or None
        """
        config = self._safe_get_attribute(node, "autoNumberConfig")
        if not config:
            return None
        if isinstance(config, str):
            try:
                config = json.loads(config)
            except json.JSONDecodeError:
                return None
        if isinstance(config, dict):
            return AutoNumberConfig(
                enabled=bool(config.get("enabled", False)),
                position=config.get("position", "start"),
                format_style=config.get("formatStyle", "#1"),
                skip_first=config.get("skipFirst", 1),
                skip_last=config.get("skipLast", 1),
            )
        return None

    def _validate_card(self, card: TwitterCardContent, index: int) -> list[str]:
        """Validate a single Twitter card.

        Args:
            card: The card to validate
            index: Position of the card for error messages

        Returns:
            List of validation errors
        """
        errors: list[str] = []
        prefix = f"Card {index + 1}"

        # Validate text length
        text_error = self._validate_text_length(
            card.text, TWITTER_MAX_CHARACTERS, f"{prefix} text"
        )
        if text_error:
            errors.append(text_error)

        # Validate media count
        if card.media:
            media_error = self._validate_media_count(
                card.media, TWITTER_MAX_MEDIA_IMAGES
            )
            if media_error:
                errors.append(f"{prefix}: {media_error}")

        return errors
