"""
SQLAlchemy storage implementation for ratings.
"""

from typing import List, Optional, Dict, Any
from sqlalchemy.orm import Session
from sqlalchemy import desc

from ..core.storage import RatingStorage
from ...database.models.news import RatingType, UserRating


class SQLRatingStorage(RatingStorage):
    """SQLAlchemy implementation of rating storage"""

    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 SQLRatingStorage")
        self._session = session

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

    def create(self, data: Dict[str, Any]) -> str:
        """Create a new rating"""
        with self.session as session:
            rating = UserRating(
                user_id=data["user_id"],
                item_id=data["item_id"],
                item_type=data.get("item_type", "card"),
                relevance_vote=data.get(
                    "rating_value"
                ),  # Map rating_value to relevance_vote
                quality_rating=data.get("quality_rating"),
            )

            session.add(rating)
            session.commit()

            return str(rating.id)

    def get(self, id: str) -> Optional[Dict[str, Any]]:
        """Get a rating by ID"""
        with self.session as session:
            rating = session.query(UserRating).filter_by(id=int(id)).first()
            if not rating:
                return None
            return {
                "id": rating.id,
                "user_id": rating.user_id,
                "item_id": rating.item_id,
                "item_type": rating.item_type,
                "relevance_vote": rating.relevance_vote,
                "quality_rating": rating.quality_rating,
                "created_at": rating.created_at,
                "updated_at": rating.updated_at,
            }

    def update(self, id: str, data: Dict[str, Any]) -> bool:
        """Update a rating"""
        with self.session as session:
            rating = session.query(UserRating).filter_by(id=int(id)).first()
            if not rating:
                return False

            # Update allowed fields
            if "rating_value" in data:
                rating.rating_value = data["rating_value"]
            if "comment" in data:
                rating.comment = data["comment"]

            session.commit()
            return True

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

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

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

            if filters:
                if "user_id" in filters:
                    query = query.filter_by(user_id=filters["user_id"])
                if "item_id" in filters:
                    query = query.filter_by(item_id=filters["item_id"])
                if "item_type" in filters:
                    query = query.filter_by(item_type=filters["item_type"])
                # For backward compatibility, also check card_id
                if "card_id" in filters:
                    query = query.filter_by(item_id=filters["card_id"])

            ratings = (
                query.order_by(desc(UserRating.created_at))
                .limit(limit)
                .offset(offset)
                .all()
            )
            return [
                {
                    "id": rating.id,
                    "user_id": rating.user_id,
                    "item_id": rating.item_id,
                    "item_type": rating.item_type,
                    "relevance_vote": rating.relevance_vote,
                    "quality_rating": rating.quality_rating,
                    "created_at": rating.created_at,
                    "updated_at": rating.updated_at,
                }
                for rating in ratings
            ]

    def get_user_rating(
        self, user_id: str, item_id: str, rating_type: str
    ) -> Optional[Dict[str, Any]]:
        """Get a user's rating for a specific item"""
        with self.session as session:
            query = session.query(UserRating).filter_by(
                user_id=user_id, rating_type=RatingType(rating_type)
            )

            # Check both card and news item IDs
            rating = query.filter(
                (UserRating.card_id == item_id)
                | (UserRating.news_item_id == item_id)
            ).first()

            return rating.to_dict() if rating else None

    def upsert_rating(
        self,
        user_id: str,
        item_id: str,
        rating_type: str,
        rating_value: str,
        item_type: str = "card",
    ) -> str:
        """Create or update a rating"""
        # Check if rating exists
        existing = self.get_user_rating(user_id, item_id, rating_type)

        if existing:
            # Update existing rating
            self.update(str(existing["id"]), {"rating_value": rating_value})
            return str(existing["id"])
        else:
            # Create new rating
            data = {
                "user_id": user_id,
                "item_id": item_id,
                "item_type": item_type,
                "rating_type": rating_type,
                "rating_value": rating_value,
            }
            return self.create(data)

    def get_ratings_summary(
        self, item_id: str, item_type: str = "card"
    ) -> Dict[str, Any]:
        """Get aggregated ratings for an item"""
        with self.session as session:
            # Build query based on item type
            if item_type == "card":
                base_query = session.query(UserRating).filter_by(
                    card_id=item_id
                )
            else:
                base_query = session.query(UserRating).filter_by(
                    news_item_id=item_id
                )

            # Get quality ratings
            quality_ratings = base_query.filter_by(
                rating_type=RatingType.QUALITY
            ).all()
            quality_values = [
                int(r.rating_value)
                for r in quality_ratings
                if r.rating_value.isdigit()
            ]

            # Get relevance ratings
            relevance_ratings = base_query.filter_by(
                rating_type=RatingType.RELEVANCE
            ).all()
            up_votes = sum(
                1 for r in relevance_ratings if r.rating_value == "up"
            )
            down_votes = sum(
                1 for r in relevance_ratings if r.rating_value == "down"
            )

            return {
                "item_id": item_id,
                "item_type": item_type,
                "quality": {
                    "count": len(quality_values),
                    "average": sum(quality_values) / len(quality_values)
                    if quality_values
                    else 0,
                    "distribution": self._get_rating_distribution(
                        quality_values
                    ),
                },
                "relevance": {
                    "up_votes": up_votes,
                    "down_votes": down_votes,
                    "net_score": up_votes - down_votes,
                },
            }

    def get_user_ratings(
        self, user_id: str, rating_type: Optional[str] = None, limit: int = 100
    ) -> List[Dict[str, Any]]:
        """Get all ratings by a user"""
        filters = {"user_id": user_id}
        if rating_type:
            filters["rating_type"] = rating_type

        return self.list(filters, limit)

    def _get_rating_distribution(self, ratings: List[int]) -> Dict[int, int]:
        """Get distribution of ratings (1-5)"""
        distribution = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0}
        for rating in ratings:
            if 1 <= rating <= 5:
                distribution[rating] += 1
        return distribution
