"""
Factory for creating and managing different types of cards.
Handles card creation, loading, and type registration.
"""

import uuid
from typing import Dict, Type, Optional, Any, List, cast
from loguru import logger

from .base_card import (
    BaseCard,
    CardSource,
    NewsCard,
    ResearchCard,
    UpdateCard,
    OverviewCard,
)
from .card_storage import SQLCardStorage
# from ..database import init_news_database  # Not needed - tables created on demand


class CardFactory:
    """
    Factory for creating and managing cards.
    Provides a unified interface for card operations.
    """

    # Registry of card types
    _card_types: Dict[str, Type[BaseCard]] = {
        "news": NewsCard,
        "research": ResearchCard,
        "update": UpdateCard,
        "overview": OverviewCard,
    }

    # Storage instance (singleton)
    _storage: Optional[SQLCardStorage] = None

    @classmethod
    def register_card_type(
        cls, type_name: str, card_class: Type[BaseCard]
    ) -> None:
        """
        Register a new card type.

        Args:
            type_name: The name identifier for this card type
            card_class: The class to use for this type
        """
        if not issubclass(card_class, BaseCard):
            raise ValueError(f"{card_class} must be a subclass of BaseCard")

        cls._card_types[type_name] = card_class
        logger.info(
            f"Registered card type: {type_name} -> {card_class.__name__}"
        )

    @classmethod
    def get_storage(cls, session=None) -> SQLCardStorage:
        """Get or create the storage instance.

        Args:
            session: SQLAlchemy session. If not provided, attempts to get
                    session from Flask context (g.db_session).

        Returns:
            SQLCardStorage instance

        Raises:
            RuntimeError: If no session is available
        """
        if session is not None:
            # Return a fresh storage with the provided session
            return SQLCardStorage(session)

        # Try to get session from Flask context
        try:
            from flask import g, has_app_context

            if has_app_context() and hasattr(g, "db_session") and g.db_session:
                return SQLCardStorage(g.db_session)
        except ImportError:
            pass

        raise RuntimeError(
            "No database session available. Pass a session to get_storage() "
            "or ensure this is called within a Flask request context with "
            "g.db_session set."
        )

    @classmethod
    def create_card(
        cls,
        card_type: str,
        topic: str,
        source: CardSource,
        user_id: str,
        **kwargs,
    ) -> BaseCard:
        """
        Create a new card of the specified type.

        Args:
            card_type: Type of card to create ('news', 'research', etc.)
            topic: The card topic
            source: Source information for the card
            user_id: ID of the user creating the card
            **kwargs: Additional arguments for the specific card type

        Returns:
            The created card instance

        Raises:
            ValueError: If card_type is not registered
        """
        if card_type not in cls._card_types:
            raise ValueError(
                f"Unknown card type: {card_type}. "
                f"Available types: {list(cls._card_types.keys())}"
            )

        # Generate unique ID
        card_id = str(uuid.uuid4())

        # Create the card instance
        card_class = cls._card_types[card_type]
        card = card_class(
            card_id=card_id,
            topic=topic,
            source=source,
            user_id=user_id,
            **kwargs,
        )

        # Save to storage
        storage = cls.get_storage()
        card_data = card.to_dict()
        storage.create(card_data)

        logger.info(f"Created {card_type} card: {card_id} - {topic}")
        return card

    @classmethod
    def load_card(cls, card_id: str) -> Optional[BaseCard]:
        """
        Load a card from storage.

        Args:
            card_id: The ID of the card to load

        Returns:
            The card instance or None if not found
        """
        storage = cls.get_storage()
        card_data = storage.get(card_id)

        if not card_data:
            logger.warning(f"Card not found: {card_id}")
            return None

        # Use the helper method to reconstruct the card
        return cls._reconstruct_card(card_data)

    @classmethod
    def get_user_cards(
        cls,
        user_id: str,
        card_types: Optional[List[str]] = None,
        limit: int = 20,
        offset: int = 0,
    ) -> List[BaseCard]:
        """
        Get cards for a specific user.

        Args:
            user_id: The user ID
            card_types: Optional list of card types to filter
            limit: Maximum number of cards to return
            offset: Offset for pagination

        Returns:
            List of card instances
        """
        storage = cls.get_storage()

        # Build filter
        filters: Dict[str, Any] = {"user_id": user_id}
        if card_types:
            filters["card_type"] = card_types

        # Get card data
        cards_data = storage.list(filters=filters, limit=limit, offset=offset)

        # Reconstruct cards
        cards = []
        for data in cards_data:
            card = cls._reconstruct_card(data)
            if card:
                cards.append(card)

        return cards

    @classmethod
    def get_recent_cards(
        cls,
        hours: int = 24,
        card_types: Optional[List[str]] = None,
        limit: int = 50,
    ) -> List[BaseCard]:
        """
        Get recent cards across all users.

        Args:
            hours: How many hours back to look
            card_types: Optional list of types to filter
            limit: Maximum number of cards

        Returns:
            List of recent cards
        """
        storage = cls.get_storage()

        # Get recent card data
        cards_data = storage.get_recent(
            hours=hours, card_types=card_types, limit=limit
        )

        # Reconstruct cards
        cards = []
        for data in cards_data:
            card = cls._reconstruct_card(data)
            if card:
                cards.append(card)

        return cards

    @classmethod
    def update_card(cls, card: BaseCard, session=None) -> bool:
        """
        Update a card in storage.

        Args:
            card: The card to update
            session: Optional SQLAlchemy session

        Returns:
            True if successful
        """
        storage = cls.get_storage(session)
        # Convert card to dict for storage.update() which expects (id, data)
        card_data = card.to_dict()
        # Include interaction data for updates
        card_data["interaction"] = card.interaction
        return storage.update(card.id, card_data)

    @classmethod
    def delete_card(cls, card_id: str) -> bool:
        """
        Delete a card from storage.

        Args:
            card_id: ID of the card to delete

        Returns:
            True if successful
        """
        storage = cls.get_storage()
        return storage.delete(card_id)

    @classmethod
    def _reconstruct_card(cls, card_data: Dict[str, Any]) -> Optional[BaseCard]:
        """
        Helper to reconstruct a card from storage data.

        Args:
            card_data: Dictionary of card data

        Returns:
            Reconstructed card or None
        """
        from datetime import datetime

        try:
            # Handle enum card_type - could be string or enum
            card_type = card_data.get("card_type", "news")
            if hasattr(card_type, "value"):
                card_type = card_type.value

            if card_type not in cls._card_types:
                logger.error(f"Unknown card type: {card_type}")
                return None

            # Extract source - may be dict or may need construction
            source_data = card_data.get("source", {})
            if not source_data:
                # Construct from flat fields
                source_data = {
                    "type": card_data.get("source_type", "unknown"),
                    "source_id": card_data.get("source_id"),
                    "created_from": card_data.get("created_from", ""),
                    "metadata": {},
                }
            source = CardSource(
                type=source_data.get("type", "unknown"),
                source_id=source_data.get("source_id"),
                created_from=source_data.get("created_from", ""),
                metadata=source_data.get("metadata", {}),
            )

            # Get user_id, handling None case
            user_id = card_data.get("user_id") or "unknown"

            # Create card
            card_class = cls._card_types[card_type]
            card = card_class(
                card_id=card_data["id"],
                topic=card_data.get("topic") or card_data.get("title", ""),
                source=source,
                user_id=user_id,
            )

            # Restore attributes - handle ISO string dates
            created_at = card_data.get("created_at")
            if created_at:
                if isinstance(created_at, str):
                    card.created_at = datetime.fromisoformat(
                        created_at.replace("Z", "+00:00")
                    )
                else:
                    card.created_at = created_at

            updated_at = card_data.get("updated_at")
            if updated_at:
                if isinstance(updated_at, str):
                    card.updated_at = datetime.fromisoformat(
                        updated_at.replace("Z", "+00:00")
                    )
                else:
                    card.updated_at = updated_at

            card.versions = card_data.get("versions", [])
            card.metadata = card_data.get("metadata", {})
            card.interaction = card_data.get("interaction", {})

            return card

        except Exception:
            logger.exception("Error reconstructing card")
            return None

    @classmethod
    def create_news_card_from_analysis(
        cls,
        news_item: Dict[str, Any],
        source_search_id: str,
        user_id: str,
        additional_metadata: Optional[Dict[str, Any]] = None,
    ) -> NewsCard:
        """
        Create a news card from analyzed news data.

        Args:
            news_item: Dictionary with news data from analyzer
            source_search_id: ID of the search that found this
            user_id: User who initiated the search

        Returns:
            Created NewsCard
        """
        source = CardSource(
            type="news_search",
            source_id=source_search_id,
            created_from="News analysis",
            metadata={"analyzer_version": "1.0", "extraction_method": "llm"},
        )

        # Merge additional metadata if provided
        metadata = news_item.get("metadata", {})
        if additional_metadata:
            metadata.update(additional_metadata)

        # Create the news card with all the analyzed data
        card = cls.create_card(
            card_type="news",
            topic=news_item.get("headline", "Untitled"),
            source=source,
            user_id=user_id,
            category=news_item.get("category", "Other"),
            summary=news_item.get("summary", ""),
            analysis=news_item.get("analysis", ""),
            impact_score=news_item.get("impact_score", 5),
            entities=news_item.get("entities", {}),
            topics=news_item.get("topics", []),
            source_url=news_item.get("source_url", ""),
            is_developing=news_item.get("is_developing", False),
            surprising_element=news_item.get("surprising_element"),
            metadata=metadata,
        )

        return cast(NewsCard, card)


# Create convenience functions at module level
def create_card(card_type: str, **kwargs) -> BaseCard:
    """Convenience function to create a card."""
    return CardFactory.create_card(card_type, **kwargs)


def load_card(card_id: str) -> Optional[BaseCard]:
    """Convenience function to load a card."""
    return CardFactory.load_card(card_id)
