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

Tests cover:
- SQLPreferenceStorage initialization
- CRUD operations (create, read, update, delete)
- get_user_preferences() method
- upsert_preferences() method
- add_liked_item() and add_disliked_item() methods
- update_preference_embedding() method
- 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 TestSQLPreferenceStorageInit:
    """Tests for SQLPreferenceStorage initialization."""

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

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

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

        storage = SQLPreferenceStorage(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.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        storage = SQLPreferenceStorage(mock_session)

        assert storage.session is mock_session


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

    def test_creates_preference(self, mock_session):
        """Creates a preference record."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.id = 1

        with patch(
            "local_deep_research.news.preference_manager.storage.UserPreference"
        ) as MockPref:
            MockPref.return_value = mock_pref
            mock_session.add = Mock()
            mock_session.commit = Mock()

            storage = SQLPreferenceStorage(mock_session)
            result = storage.create({"user_id": "user1"})

            assert result == "1"

    def test_create_with_all_fields(self, mock_session):
        """Creates preference with all fields."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.id = 2

        with patch(
            "local_deep_research.news.preference_manager.storage.UserPreference"
        ) as MockPref:
            MockPref.return_value = mock_pref
            mock_session.add = Mock()
            mock_session.commit = Mock()

            storage = SQLPreferenceStorage(mock_session)
            data = {
                "user_id": "user1",
                "liked_categories": ["tech", "science"],
                "disliked_categories": ["sports"],
                "liked_topics": ["AI"],
                "disliked_topics": ["politics"],
                "impact_threshold": 7,
                "focus_preferences": {"topic": "tech"},
                "custom_prompt": "My prompt",
                "custom_search_terms": "AI ML",
                "preference_embedding": [0.1, 0.2],
                "liked_news_ids": ["id1"],
                "disliked_news_ids": ["id2"],
            }
            result = storage.create(data)

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


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

    def test_get_existing_preference(self, mock_session):
        """Gets existing preference by ID."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.to_dict.return_value = {"id": 1, "user_id": "user1"}
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref

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

        assert result["id"] == 1

    def test_get_nonexistent_preference(self, mock_session):
        """Returns None for nonexistent preference."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

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

        assert result is None


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

    def test_update_existing_preference(self, mock_session):
        """Updates existing preference."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.liked_categories = []
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.update("1", {"liked_categories": ["tech"]})

        assert result is True

    def test_update_nonexistent_preference(self, mock_session):
        """Returns False for nonexistent preference."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        storage = SQLPreferenceStorage(mock_session)
        result = storage.update("999", {"liked_categories": ["tech"]})

        assert result is False


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

    def test_delete_existing_preference(self, mock_session):
        """Deletes existing preference."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

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

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

    def test_delete_nonexistent_preference(self, mock_session):
        """Returns False for nonexistent preference."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

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

        assert result is False


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

    def test_list_all_preferences(self, mock_session):
        """Lists all preferences."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref1 = Mock()
        mock_pref1.to_dict.return_value = {"id": 1, "user_id": "user1"}
        mock_pref2 = Mock()
        mock_pref2.to_dict.return_value = {"id": 2, "user_id": "user2"}

        mock_query = MagicMock()
        mock_query.limit.return_value.offset.return_value.all.return_value = [
            mock_pref1,
            mock_pref2,
        ]
        mock_session.query.return_value = mock_query

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

        assert len(result) == 2

    def test_list_with_user_filter(self, mock_session):
        """Lists preferences filtered by user_id."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.to_dict.return_value = {"id": 1, "user_id": "user1"}

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

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

        assert len(result) == 1

    def test_list_with_limit(self, mock_session):
        """Lists preferences with limit."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        storage = SQLPreferenceStorage(mock_session)
        storage.list(limit=10)

        mock_query.limit.assert_called_with(10)

    def test_list_with_offset(self, mock_session):
        """Lists preferences with offset."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        storage = SQLPreferenceStorage(mock_session)
        storage.list(offset=5)

        mock_query.limit.return_value.offset.assert_called_with(5)


class TestGetUserPreferences:
    """Tests for get_user_preferences() method."""

    def test_get_existing_user_preferences(self, mock_session):
        """Gets preferences for existing user."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.to_dict.return_value = {
            "id": 1,
            "user_id": "user1",
            "liked_categories": ["tech"],
        }
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref

        storage = SQLPreferenceStorage(mock_session)
        result = storage.get_user_preferences("user1")

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

    def test_get_nonexistent_user_preferences(self, mock_session):
        """Returns None for user without preferences."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        storage = SQLPreferenceStorage(mock_session)
        result = storage.get_user_preferences("new_user")

        assert result is None


class TestUpsertPreferences:
    """Tests for upsert_preferences() method."""

    def test_creates_when_not_exists(self, mock_session):
        """Creates preferences when user has none."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        mock_pref = Mock()
        mock_pref.id = 1

        with patch(
            "local_deep_research.news.preference_manager.storage.UserPreference"
        ) as MockPref:
            MockPref.return_value = mock_pref
            mock_session.add = Mock()
            mock_session.commit = Mock()

            storage = SQLPreferenceStorage(mock_session)
            result = storage.upsert_preferences(
                "new_user", {"liked_categories": ["tech"]}
            )

            assert result == "1"

    def test_updates_when_exists(self, mock_session):
        """Updates preferences when user has existing ones."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.id = 1
        mock_pref.to_dict.return_value = {"id": 1, "user_id": "user1"}
        mock_pref.liked_categories = []

        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.upsert_preferences(
            "user1", {"liked_categories": ["tech"]}
        )

        assert result == "1"


class TestAddLikedItem:
    """Tests for add_liked_item() method."""

    def test_adds_liked_news_to_existing_prefs(self, mock_session):
        """Adds liked news item to existing preferences."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.liked_news_ids = ["id1"]
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.add_liked_item("user1", "id2", item_type="news")

        assert result is True
        assert "id2" in mock_pref.liked_news_ids

    def test_creates_prefs_when_none_exist(self, mock_session):
        """Creates preferences when user has none."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        with patch(
            "local_deep_research.news.preference_manager.storage.UserPreference"
        ):
            storage = SQLPreferenceStorage(mock_session)
            result = storage.add_liked_item("new_user", "id1", item_type="news")

            assert result is True
            mock_session.add.assert_called_once()

    def test_does_not_add_duplicate(self, mock_session):
        """Does not add duplicate liked item."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.liked_news_ids = ["id1"]
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        storage.add_liked_item("user1", "id1", item_type="news")

        # Should still be only one item
        assert mock_pref.liked_news_ids.count("id1") == 1

    def test_limits_to_100_items(self, mock_session):
        """Limits liked items to last 100."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.liked_news_ids = [f"id{i}" for i in range(100)]
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        storage.add_liked_item("user1", "new_id", item_type="news")

        assert len(mock_pref.liked_news_ids) == 100


class TestAddDislikedItem:
    """Tests for add_disliked_item() method."""

    def test_adds_disliked_news_to_existing_prefs(self, mock_session):
        """Adds disliked news item to existing preferences."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.disliked_news_ids = ["id1"]
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.add_disliked_item("user1", "id2", item_type="news")

        assert result is True
        assert "id2" in mock_pref.disliked_news_ids

    def test_creates_prefs_when_none_exist_dislike(self, mock_session):
        """Creates preferences when user has none for dislike."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        with patch(
            "local_deep_research.news.preference_manager.storage.UserPreference"
        ):
            storage = SQLPreferenceStorage(mock_session)
            result = storage.add_disliked_item(
                "new_user", "id1", item_type="news"
            )

            assert result is True
            mock_session.add.assert_called_once()

    def test_limits_disliked_to_100_items(self, mock_session):
        """Limits disliked items to last 100."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.disliked_news_ids = [f"id{i}" for i in range(100)]
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        storage.add_disliked_item("user1", "new_id", item_type="news")

        assert len(mock_pref.disliked_news_ids) == 100


class TestUpdatePreferenceEmbedding:
    """Tests for update_preference_embedding() method."""

    def test_updates_existing_embedding(self, mock_session):
        """Updates embedding for existing preferences."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.preference_embedding = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.update_preference_embedding("user1", [0.1, 0.2, 0.3])

        assert result is True
        assert mock_pref.preference_embedding == [0.1, 0.2, 0.3]

    def test_creates_prefs_for_embedding(self, mock_session):
        """Creates preferences when user has none for embedding."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

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

        with patch(
            "local_deep_research.news.preference_manager.storage.UserPreference"
        ):
            storage = SQLPreferenceStorage(mock_session)
            result = storage.update_preference_embedding("new_user", [0.1, 0.2])

            assert result is True
            mock_session.add.assert_called_once()


class TestPreferenceStorageEdgeCases:
    """Edge case tests for SQLPreferenceStorage."""

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

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

        storage = SQLPreferenceStorage(mock_session)
        result = storage.get_user_preferences("")

        assert result is None

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

        mock_pref = Mock()
        mock_pref.to_dict.return_value = {"user_id": "用户"}
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref

        storage = SQLPreferenceStorage(mock_session)
        result = storage.get_user_preferences("用户")

        assert result is not None

    def test_empty_embedding_list(self, mock_session):
        """Handles empty embedding list."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.update_preference_embedding("user1", [])

        assert result is True

    def test_none_liked_news_ids(self, mock_session):
        """Handles None liked_news_ids."""
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_pref = Mock()
        mock_pref.liked_news_ids = None
        mock_session.query.return_value.filter_by.return_value.first.return_value = mock_pref
        mock_session.commit = Mock()

        storage = SQLPreferenceStorage(mock_session)
        result = storage.add_liked_item("user1", "id1", item_type="news")

        assert result is True
