"""
Extended tests for news/rating_system/storage.py

Tests cover:
- SQLRatingStorage initialization
- CRUD operations (create, read, update, delete)
- get_user_rating() method
- upsert_rating() method
- get_ratings_summary() method
- get_user_ratings() method
- _get_rating_distribution() helper
- list() with filters
"""

import pytest
from unittest.mock import Mock, MagicMock, patch


@pytest.fixture
def mock_session():
    """Create a mock database session."""
    session = MagicMock()
    session.__enter__ = Mock(return_value=session)
    session.__exit__ = Mock(return_value=False)
    return session


class TestSQLRatingStorageInit:
    """Tests for SQLRatingStorage initialization."""

    def test_raises_without_session(self):
        """Raises ValueError when session is None."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        with pytest.raises(ValueError, match="Session is required"):
            SQLRatingStorage(None)

    def test_stores_session(self, mock_session):
        """Stores the session."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)

        assert storage._session is mock_session

    def test_session_property(self, mock_session):
        """Session property returns the session."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)

        assert storage.session is mock_session


class TestSQLRatingStorageCreate:
    """Tests for create() method."""

    def test_creates_rating(self, mock_session):
        """Creates a rating record."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.id = 1

        with patch(
            "local_deep_research.news.rating_system.storage.UserRating"
        ) as MockRating:
            MockRating.return_value = mock_rating
            mock_session.add = Mock()
            mock_session.commit = Mock()

            storage = SQLRatingStorage(mock_session)
            result = storage.create(
                {
                    "user_id": "user1",
                    "item_id": "card1",
                    "rating_value": "up",
                }
            )

            assert result == "1"

    def test_create_with_all_fields(self, mock_session):
        """Creates rating with all fields."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.id = 2

        with patch(
            "local_deep_research.news.rating_system.storage.UserRating"
        ) as MockRating:
            MockRating.return_value = mock_rating
            mock_session.add = Mock()
            mock_session.commit = Mock()

            storage = SQLRatingStorage(mock_session)
            data = {
                "user_id": "user1",
                "item_id": "card1",
                "item_type": "card",
                "rating_value": "up",
                "quality_rating": 5,
            }
            result = storage.create(data)

            assert result == "2"
            MockRating.assert_called_once()


class TestSQLRatingStorageGet:
    """Tests for get() method."""

    def test_get_existing_rating(self, mock_session):
        """Gets existing rating by ID."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.id = 1
        mock_rating.user_id = "user1"
        mock_rating.item_id = "card1"
        mock_rating.item_type = "card"
        mock_rating.relevance_vote = "up"
        mock_rating.quality_rating = None
        mock_rating.created_at = None
        mock_rating.updated_at = None

        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_rating

        storage = SQLRatingStorage(mock_session)
        result = storage.get("1")

        assert result["id"] == 1
        assert result["user_id"] == "user1"

    def test_get_nonexistent_rating(self, mock_session):
        """Returns None for nonexistent rating."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        storage = SQLRatingStorage(mock_session)
        result = storage.get("999")

        assert result is None


class TestSQLRatingStorageUpdate:
    """Tests for update() method."""

    def test_update_existing_rating(self, mock_session):
        """Updates existing rating."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.rating_value = "up"
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_rating
        mock_session.commit = Mock()

        storage = SQLRatingStorage(mock_session)
        result = storage.update("1", {"rating_value": "down"})

        assert result is True

    def test_update_nonexistent_rating(self, mock_session):
        """Returns False for nonexistent rating."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        storage = SQLRatingStorage(mock_session)
        result = storage.update("999", {"rating_value": "down"})

        assert result is False

    def test_update_with_comment(self, mock_session):
        """Updates rating with comment."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.comment = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_rating
        mock_session.commit = Mock()

        storage = SQLRatingStorage(mock_session)
        result = storage.update("1", {"comment": "Great article!"})

        assert result is True


class TestSQLRatingStorageDelete:
    """Tests for delete() method."""

    def test_delete_existing_rating(self, mock_session):
        """Deletes existing rating."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_rating
        mock_session.delete = Mock()
        mock_session.commit = Mock()

        storage = SQLRatingStorage(mock_session)
        result = storage.delete("1")

        assert result is True
        mock_session.delete.assert_called_once_with(mock_rating)

    def test_delete_nonexistent_rating(self, mock_session):
        """Returns False for nonexistent rating."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_session.query.return_value.filter_by.return_value.first.return_value = None

        storage = SQLRatingStorage(mock_session)
        result = storage.delete("999")

        assert result is False


class TestSQLRatingStorageList:
    """Tests for list() method."""

    def test_list_all_ratings(self, mock_session):
        """Lists all ratings."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating1 = Mock()
        mock_rating1.id = 1
        mock_rating1.user_id = "user1"
        mock_rating1.item_id = "card1"
        mock_rating1.item_type = "card"
        mock_rating1.relevance_vote = "up"
        mock_rating1.quality_rating = None
        mock_rating1.created_at = None
        mock_rating1.updated_at = None

        mock_rating2 = Mock()
        mock_rating2.id = 2
        mock_rating2.user_id = "user2"
        mock_rating2.item_id = "card2"
        mock_rating2.item_type = "card"
        mock_rating2.relevance_vote = "down"
        mock_rating2.quality_rating = 3
        mock_rating2.created_at = None
        mock_rating2.updated_at = None

        mock_query = MagicMock()
        mock_query.order_by.return_value.limit.return_value.offset.return_value.all.return_value = [
            mock_rating1,
            mock_rating2,
        ]
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.list()

        assert len(result) == 2

    def test_list_with_user_filter(self, mock_session):
        """Lists ratings filtered by user_id."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.id = 1
        mock_rating.user_id = "user1"
        mock_rating.item_id = "card1"
        mock_rating.item_type = "card"
        mock_rating.relevance_vote = "up"
        mock_rating.quality_rating = None
        mock_rating.created_at = None
        mock_rating.updated_at = None

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = [
            mock_rating
        ]
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.list(filters={"user_id": "user1"})

        assert len(result) == 1

    def test_list_with_item_filter(self, mock_session):
        """Lists ratings filtered by item_id."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        storage.list(filters={"item_id": "card1"})

        mock_query.filter_by.assert_called()

    def test_list_with_card_id_filter(self, mock_session):
        """Lists ratings filtered by card_id (backward compat)."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        storage.list(filters={"card_id": "card1"})

        # Should accept card_id for backward compatibility
        assert True


class TestGetUserRating:
    """Tests for get_user_rating() method."""

    def test_method_exists(self, mock_session):
        """get_user_rating method exists."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)

        assert hasattr(storage, "get_user_rating")
        assert callable(storage.get_user_rating)

    def test_method_signature(self, mock_session):
        """get_user_rating has correct signature."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )
        import inspect

        storage = SQLRatingStorage(mock_session)
        sig = inspect.signature(storage.get_user_rating)

        params = list(sig.parameters.keys())
        assert "user_id" in params
        assert "item_id" in params
        assert "rating_type" in params


class TestUpsertRating:
    """Tests for upsert_rating() method."""

    def test_method_exists(self, mock_session):
        """upsert_rating method exists."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)

        assert hasattr(storage, "upsert_rating")
        assert callable(storage.upsert_rating)

    def test_method_signature(self, mock_session):
        """upsert_rating accepts required parameters."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )
        import inspect

        storage = SQLRatingStorage(mock_session)
        sig = inspect.signature(storage.upsert_rating)

        params = list(sig.parameters.keys())
        assert "user_id" in params
        assert "item_id" in params
        assert "rating_type" in params
        assert "rating_value" in params


class TestGetRatingsSummary:
    """Tests for get_ratings_summary() method."""

    def test_returns_summary_structure(self, mock_session):
        """Returns proper summary structure."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.all.return_value = []
        mock_session.query.return_value.filter_by.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_ratings_summary("card1")

        assert "item_id" in result
        assert "quality" in result
        assert "relevance" in result

    def test_counts_up_votes(self, mock_session):
        """Counts up votes correctly."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating1 = Mock()
        mock_rating1.rating_value = "up"
        mock_rating2 = Mock()
        mock_rating2.rating_value = "up"
        mock_rating3 = Mock()
        mock_rating3.rating_value = "down"

        mock_query = MagicMock()
        mock_query.filter_by.return_value.all.side_effect = [
            [],  # quality ratings
            [mock_rating1, mock_rating2, mock_rating3],  # relevance ratings
        ]
        mock_session.query.return_value.filter_by.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_ratings_summary("card1")

        assert result["relevance"]["up_votes"] == 2
        assert result["relevance"]["down_votes"] == 1

    def test_calculates_net_score(self, mock_session):
        """Calculates net score correctly."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating1 = Mock()
        mock_rating1.rating_value = "up"
        mock_rating2 = Mock()
        mock_rating2.rating_value = "down"
        mock_rating3 = Mock()
        mock_rating3.rating_value = "down"

        mock_query = MagicMock()
        mock_query.filter_by.return_value.all.side_effect = [
            [],
            [mock_rating1, mock_rating2, mock_rating3],
        ]
        mock_session.query.return_value.filter_by.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_ratings_summary("card1")

        assert result["relevance"]["net_score"] == -1

    def test_calculates_quality_average(self, mock_session):
        """Calculates quality rating average."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating1 = Mock()
        mock_rating1.rating_value = "4"
        mock_rating2 = Mock()
        mock_rating2.rating_value = "5"

        mock_query = MagicMock()
        mock_query.filter_by.return_value.all.side_effect = [
            [mock_rating1, mock_rating2],
            [],
        ]
        mock_session.query.return_value.filter_by.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_ratings_summary("card1")

        assert result["quality"]["average"] == 4.5
        assert result["quality"]["count"] == 2


class TestGetUserRatings:
    """Tests for get_user_ratings() method."""

    def test_gets_all_user_ratings(self, mock_session):
        """Gets all ratings for a user."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.id = 1
        mock_rating.user_id = "user1"
        mock_rating.item_id = "card1"
        mock_rating.item_type = "card"
        mock_rating.relevance_vote = "up"
        mock_rating.quality_rating = None
        mock_rating.created_at = None
        mock_rating.updated_at = None

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = [
            mock_rating
        ]
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_user_ratings("user1")

        assert len(result) == 1

    def test_gets_user_ratings_by_type(self, mock_session):
        """Gets user ratings filtered by type."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        storage.get_user_ratings("user1", rating_type="quality")

        # Should filter by rating_type
        assert True

    def test_respects_limit(self, mock_session):
        """Respects limit parameter."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        storage.get_user_ratings("user1", limit=50)

        mock_query.filter_by.return_value.order_by.return_value.limit.assert_called_with(
            50
        )


class TestGetRatingDistribution:
    """Tests for _get_rating_distribution() helper."""

    def test_distribution_structure(self, mock_session):
        """Returns distribution with 1-5 keys."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)
        result = storage._get_rating_distribution([1, 2, 3, 4, 5])

        assert 1 in result
        assert 2 in result
        assert 3 in result
        assert 4 in result
        assert 5 in result

    def test_counts_ratings(self, mock_session):
        """Counts ratings correctly."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)
        result = storage._get_rating_distribution([5, 5, 5, 4, 3])

        assert result[5] == 3
        assert result[4] == 1
        assert result[3] == 1

    def test_ignores_out_of_range(self, mock_session):
        """Ignores ratings outside 1-5 range."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)
        result = storage._get_rating_distribution([0, 6, 10, 3])

        assert result[3] == 1
        assert result[1] == 0

    def test_empty_list(self, mock_session):
        """Handles empty list."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        storage = SQLRatingStorage(mock_session)
        result = storage._get_rating_distribution([])

        assert sum(result.values()) == 0


class TestRatingStorageEdgeCases:
    """Edge case tests for SQLRatingStorage."""

    def test_empty_user_id(self, mock_session):
        """Handles empty user_id."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_user_ratings("")

        assert result == []

    def test_unicode_user_id(self, mock_session):
        """Handles unicode user_id."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_user_ratings("用户")

        assert isinstance(result, list)

    def test_very_large_limit(self, mock_session):
        """Handles very large limit."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_query = MagicMock()
        mock_query.filter_by.return_value.order_by.return_value.limit.return_value.offset.return_value.all.return_value = []
        mock_session.query.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_user_ratings("user1", limit=10000)

        assert isinstance(result, list)

    def test_quality_rating_with_non_digit(self, mock_session):
        """Handles non-digit quality rating values."""
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_rating = Mock()
        mock_rating.rating_value = "invalid"

        mock_query = MagicMock()
        mock_query.filter_by.return_value.all.side_effect = [
            [mock_rating],
            [],
        ]
        mock_session.query.return_value.filter_by.return_value = mock_query

        storage = SQLRatingStorage(mock_session)
        result = storage.get_ratings_summary("card1")

        # Non-digit ratings should be filtered out
        assert result["quality"]["count"] == 0
