"""
SQLAlchemy storage implementation for news cards.

Note: This module aligns with the NewsCard SQLAlchemy model in database/models/news.py.
The NewsCard model has these relevant fields:
- id, title, summary, content, url
- source_name, source_type, source_id
- category, tags, card_type
- published_at, discovered_at
- is_read, read_at, is_saved, saved_at
- extra_data, subscription_id
"""

from datetime import datetime, timedelta, timezone
from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import desc
from loguru import logger

from .storage import CardStorage
from ...database.models.news import NewsCard


class SQLCardStorage(CardStorage):
    """SQLAlchemy implementation of card storage.

    Maps between the card system's data model and the NewsCard database model.
    Some fields from the card system are stored in extra_data JSON field.
    """

    def __init__(self, session: Session):
        """Initialize with a database session from the user's encrypted database"""
        if not session:
            raise ValueError("Session is required for SQLCardStorage")
        self._session = session

    @property
    def session(self):
        """Get database session"""
        return self._session

    def create(self, data: Dict[str, Any]) -> str:
        """Create a new card.

        Maps card system fields to NewsCard model:
        - topic → title
        - user_id, parent_card_id, created_from → stored in extra_data
        """
        card_id = data.get("id") or self.generate_id()

        # Extract source info if it's nested
        source_info = data.get("source", {})
        if isinstance(source_info, dict):
            source_type = source_info.get("type")
            source_id = source_info.get("source_id")
            created_from = source_info.get("created_from")
        else:
            source_type = data.get("source_type")
            source_id = data.get("source_id")
            created_from = data.get("created_from")

        # Map card_type enum properly
        card_type_str = data.get("card_type", data.get("type", "news"))

        # Store extended fields in extra_data
        extra_data = data.get("extra_data", {}) or {}
        extra_data.update(
            {
                "user_id": data.get("user_id"),
                "parent_card_id": data.get("parent_card_id"),
                "created_from": created_from,
                "metadata": data.get("metadata", {}),
                "interaction": data.get("interaction", {}),
            }
        )

        with self.session as session:
            card = NewsCard(
                id=card_id,
                title=data.get("topic", data.get("title", "Untitled")),
                summary=data.get("summary"),
                content=data.get("content"),
                url=data.get("url", data.get("source_url")),
                source_name=data.get("source_name"),
                source_type=source_type,
                source_id=source_id,
                category=data.get("category"),
                tags=data.get("tags"),
                card_type=card_type_str,
                extra_data=extra_data,
            )

            session.add(card)
            session.commit()

            user_id = data.get("user_id", "unknown")
            logger.info(f"Created card {card_id} for user {user_id}")
            return card_id

    def get(self, id: str) -> Optional[Dict[str, Any]]:
        """Get a card by ID"""
        with self.session as session:
            card = session.query(NewsCard).filter_by(id=id).first()
            if not card:
                return None
            return self._card_to_dict(card)

    def update(self, id: str, data: Dict[str, Any]) -> bool:
        """Update a card.

        Maps card system fields to NewsCard model:
        - is_archived → stored in extra_data
        - is_pinned → is_saved
        - last_viewed → read_at (and sets is_read=True)
        """
        with self.session as session:
            card = session.query(NewsCard).filter_by(id=id).first()
            if not card:
                return False

            # Map is_pinned to is_saved
            if "is_pinned" in data:
                card.is_saved = data["is_pinned"]
                if data["is_pinned"]:
                    card.saved_at = datetime.now(timezone.utc)

            # Map last_viewed to read_at
            if "last_viewed" in data:
                card.is_read = True
                card.read_at = data["last_viewed"]

            # Store is_archived and other custom fields in extra_data
            extra_data = card.extra_data or {}
            if "is_archived" in data:
                extra_data["is_archived"] = data["is_archived"]
            if "interaction" in data:
                extra_data["interaction"] = data["interaction"]
            card.extra_data = extra_data

            session.commit()
            return True

    def delete(self, id: str) -> bool:
        """Delete a card"""
        with self.session as session:
            card = session.query(NewsCard).filter_by(id=id).first()
            if not card:
                return False

            session.delete(card)
            session.commit()
            return True

    def list(
        self,
        filters: Optional[Dict[str, Any]] = None,
        limit: int = 100,
        offset: int = 0,
    ) -> List[Dict[str, Any]]:
        """List cards with optional filtering.

        Supported filters:
        - user_id: Filter by user (stored in extra_data)
        - card_type: Filter by card type
        - is_archived: Filter by archived status (in extra_data)
        - is_pinned: Filter by pinned/saved status
        - category: Filter by category
        """
        with self.session as session:
            query = session.query(NewsCard)

            if filters:
                if "card_type" in filters:
                    card_type_val = filters["card_type"]
                    # Handle both string and list of types
                    if isinstance(card_type_val, list):
                        query = query.filter(
                            NewsCard.card_type.in_(card_type_val)
                        )
                    else:
                        query = query.filter_by(card_type=card_type_val)
                if "is_pinned" in filters:
                    query = query.filter_by(is_saved=filters["is_pinned"])
                if "category" in filters:
                    query = query.filter_by(category=filters["category"])
                # Note: user_id and is_archived filtering would require
                # JSON querying which varies by database backend

            # Order by discovery date (newest first)
            query = query.order_by(desc(NewsCard.discovered_at))

            cards = query.limit(limit).offset(offset).all()
            return [self._card_to_dict(card) for card in cards]

    def get_recent(
        self,
        hours: int = 24,
        card_types: Optional[List[str]] = None,
        limit: int = 50,
    ) -> List[Dict[str, Any]]:
        """Get recent cards within the specified time window.

        Args:
            hours: How many hours back to look (default 24)
            card_types: Optional list of card types to filter
            limit: Maximum number of cards to return

        Returns:
            List of card dictionaries
        """
        cutoff = datetime.now(timezone.utc) - timedelta(hours=hours)

        with self.session as session:
            query = session.query(NewsCard).filter(
                NewsCard.discovered_at >= cutoff
            )

            if card_types:
                query = query.filter(NewsCard.card_type.in_(card_types))

            query = query.order_by(desc(NewsCard.discovered_at))
            cards = query.limit(limit).all()

            return [self._card_to_dict(card) for card in cards]

    def _card_to_dict(self, card: NewsCard) -> Dict[str, Any]:
        """Convert a NewsCard model to the dictionary format expected by the card system.

        Maps NewsCard model fields back to card system format:
        - title → topic
        - is_saved → is_pinned
        - extra_data fields → top-level fields
        """
        extra_data: Dict[str, Any] = (
            dict(card.extra_data) if card.extra_data else {}
        )

        return {
            "id": card.id,
            "topic": card.title,  # Map title back to topic
            "title": card.title,
            "summary": card.summary,
            "content": card.content,
            "url": card.url,
            "source_name": card.source_name,
            "source_type": card.source_type,
            "source_id": card.source_id,
            "category": card.category,
            "tags": card.tags,
            "card_type": card.card_type,
            "published_at": card.published_at.isoformat()
            if card.published_at
            else None,
            "discovered_at": card.discovered_at.isoformat()
            if card.discovered_at
            else None,
            "created_at": card.discovered_at.isoformat()
            if card.discovered_at
            else None,  # Alias for compatibility
            "updated_at": card.discovered_at.isoformat()
            if card.discovered_at
            else None,  # Best approximation
            "is_read": card.is_read,
            "read_at": card.read_at.isoformat() if card.read_at else None,
            "is_saved": card.is_saved,
            "is_pinned": card.is_saved,  # Alias for compatibility
            "saved_at": card.saved_at.isoformat() if card.saved_at else None,
            # Fields from extra_data
            "user_id": extra_data.get("user_id"),
            "parent_card_id": extra_data.get("parent_card_id"),
            "created_from": extra_data.get("created_from"),
            "is_archived": extra_data.get("is_archived", False),
            "metadata": extra_data.get("metadata", {}),
            "interaction": extra_data.get("interaction", {}),
            "source": {
                "type": card.source_type,
                "source_id": card.source_id,
                "created_from": extra_data.get("created_from", ""),
                "metadata": extra_data.get("metadata", {}),
            },
        }

    def get_by_user(
        self, user_id: str, limit: int = 50, offset: int = 0
    ) -> List[Dict[str, Any]]:
        """Get cards for a specific user.

        Note: Since user_id is stored in extra_data JSON, this does a
        post-filter. For better performance with large datasets,
        consider adding a proper user_id column.
        """
        # Get more cards than needed to account for filtering
        all_cards = self.list(filters=None, limit=limit * 3, offset=0)

        # Filter by user_id from extra_data
        user_cards = [
            card
            for card in all_cards
            if card.get("user_id") == user_id
            and not card.get("is_archived", False)
        ]

        # Apply pagination
        return user_cards[offset : offset + limit]

    def get_latest_version(self, card_id: str) -> Optional[Dict[str, Any]]:
        """Get the latest version of a card.

        Note: The versioning system is not yet implemented at the database level.
        CardVersion is a Python dataclass for in-memory use, not a SQLAlchemy model.
        This method returns version info stored in extra_data if available.
        """
        card_data = self.get(card_id)
        if not card_data:
            return None

        # Check if version info is stored in extra_data
        extra_data = card_data.get("metadata", {})
        if "latest_version" in extra_data:
            result: Dict[str, Any] = extra_data["latest_version"]
            return result

        # Return card's current state as version 1
        return {
            "version_id": f"{card_id}_v1",
            "version_number": 1,
            "headline": card_data.get("title"),
            "summary": card_data.get("summary"),
            "card_id": card_id,
        }

    def add_version(self, card_id: str, version_data: Dict[str, Any]) -> str:
        """Add a new version to a card.

        Note: The versioning system stores version data in the card's extra_data
        field since CardVersion is not a database model. For full versioning
        support, a CardVersion SQLAlchemy model would need to be created.
        """
        version_id = version_data.get("id") or self.generate_id()

        with self.session as session:
            card = session.query(NewsCard).filter_by(id=card_id).first()
            if not card:
                raise ValueError(f"Card {card_id} not found")

            # Get current version count from extra_data
            extra_data = card.extra_data or {}
            versions = extra_data.get("versions", [])
            version_number = len(versions) + 1

            # Create version record
            version_record = {
                "id": version_id,
                "version_number": version_number,
                "search_query": version_data.get("search_query"),
                "headline": version_data.get("headline"),
                "summary": version_data.get("summary"),
                "findings": version_data.get("findings"),
                "sources": version_data.get("sources"),
                "impact_score": version_data.get("impact_score"),
                "topics": version_data.get("topics"),
                "entities": version_data.get("entities"),
                "created_at": datetime.now(timezone.utc).isoformat(),
            }

            versions.append(version_record)
            extra_data["versions"] = versions
            extra_data["latest_version"] = version_record

            # Update card fields with latest version info
            if version_data.get("headline"):
                card.title = version_data["headline"]
            if version_data.get("summary"):
                card.summary = version_data["summary"]

            card.extra_data = extra_data
            session.commit()

            logger.info(f"Added version {version_number} to card {card_id}")
            return version_id

    def update_latest_info(
        self, card_id: str, version_data: Dict[str, Any]
    ) -> bool:
        """Update the denormalized latest version info on the card.

        Updates the card's main fields with the latest version data.
        """
        with self.session as session:
            card = session.query(NewsCard).filter_by(id=card_id).first()
            if not card:
                return False

            # Update card's display fields
            if version_data.get("headline"):
                card.title = version_data["headline"]
            if version_data.get("summary"):
                card.summary = version_data["summary"]

            # Store version metadata in extra_data
            extra_data = card.extra_data or {}
            extra_data["latest_version"] = {
                "id": version_data.get("id"),
                "headline": version_data.get("headline"),
                "summary": version_data.get("summary"),
                "impact_score": version_data.get("impact_score"),
            }
            card.extra_data = extra_data

            session.commit()
            return True

    def archive_card(self, card_id: str) -> bool:
        """Archive a card"""
        return self.update(card_id, {"is_archived": True})

    def pin_card(self, card_id: str, pinned: bool = True) -> bool:
        """Pin or unpin a card"""
        return self.update(card_id, {"is_pinned": pinned})
