"""
Extended tests for news/core/storage_manager.py

Tests cover:
- StorageManager initialization
- Property accessors (cards, subscriptions, ratings, preferences)
- get_user_feed() - personalized feed generation
- get_trending_news() - trending news retrieval
- record_interaction() - interaction recording for all types
- get_user_subscriptions() - subscription retrieval
- get_user_stats() - statistics calculation
- get_card() - single card retrieval
- get_card_interactions() - card interaction history
- update_card() - card updates
- cleanup_old_data() - data cleanup
- InteractionType enum
- get_storage_manager() singleton
"""

import pytest
from unittest.mock import MagicMock, patch


class TestInteractionTypeEnum:
    """Tests for InteractionType enum."""

    def test_view_value(self):
        """VIEW has correct value."""
        from local_deep_research.news.core.storage_manager import (
            InteractionType,
        )

        assert InteractionType.VIEW.value == "view"

    def test_vote_up_value(self):
        """VOTE_UP has correct value."""
        from local_deep_research.news.core.storage_manager import (
            InteractionType,
        )

        assert InteractionType.VOTE_UP.value == "vote_up"

    def test_vote_down_value(self):
        """VOTE_DOWN has correct value."""
        from local_deep_research.news.core.storage_manager import (
            InteractionType,
        )

        assert InteractionType.VOTE_DOWN.value == "vote_down"

    def test_research_value(self):
        """RESEARCH has correct value."""
        from local_deep_research.news.core.storage_manager import (
            InteractionType,
        )

        assert InteractionType.RESEARCH.value == "research"

    def test_share_value(self):
        """SHARE has correct value."""
        from local_deep_research.news.core.storage_manager import (
            InteractionType,
        )

        assert InteractionType.SHARE.value == "share"

    def test_all_types_unique(self):
        """All interaction types have unique values."""
        from local_deep_research.news.core.storage_manager import (
            InteractionType,
        )

        values = [t.value for t in InteractionType]
        assert len(values) == len(set(values))


class TestStorageManagerInit:
    """Tests for StorageManager initialization."""

    def test_init_creates_none_storages(self):
        """Initialization sets storage interfaces to None."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        assert manager._cards is None
        assert manager._subscriptions is None
        assert manager._ratings is None
        assert manager._preferences is None

    def test_init_sets_card_factory(self):
        """Initialization sets card_factory reference."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        assert manager.card_factory is CardFactory

    def test_init_gets_relevance_service(self):
        """Initialization gets relevance service."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        assert manager.relevance_service is not None


class TestStorageManagerGetCurrentSession:
    """Tests for _get_current_session method."""

    def test_get_current_session_with_flask_context(self):
        """Returns session from Flask g when available."""
        from local_deep_research.news.core.storage_manager import StorageManager

        mock_session = MagicMock()
        mock_g = MagicMock()
        mock_g.db_session = mock_session

        manager = StorageManager()

        with patch(
            "local_deep_research.news.core.storage_manager.has_app_context",
            return_value=True,
        ):
            with patch(
                "local_deep_research.news.core.storage_manager.g", mock_g
            ):
                result = manager._get_current_session()
                assert result is mock_session

    def test_get_current_session_without_flask_context(self):
        """Returns None when not in Flask context."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        with patch(
            "local_deep_research.news.core.storage_manager.has_app_context",
            return_value=False,
        ):
            result = manager._get_current_session()
            assert result is None


class TestStorageManagerCardsProperty:
    """Tests for cards property accessor."""

    def test_cards_with_session_returns_sql_storage(self):
        """Returns SQLCardStorage when session available."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_storage import SQLCardStorage

        mock_session = MagicMock()
        manager = StorageManager()

        with patch.object(
            manager, "_get_current_session", return_value=mock_session
        ):
            cards = manager.cards
            assert isinstance(cards, SQLCardStorage)
            assert cards._session is mock_session

    def test_cards_without_session_raises_runtime_error(self):
        """Raises RuntimeError when no session and no cached storage."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        with patch.object(manager, "_get_current_session", return_value=None):
            with pytest.raises(RuntimeError) as exc_info:
                _ = manager.cards

            assert "No database session available" in str(exc_info.value)


class TestStorageManagerSubscriptionsProperty:
    """Tests for subscriptions property accessor."""

    def test_subscriptions_with_session_returns_sql_storage(self):
        """Returns SQLSubscriptionStorage when session available."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.subscription_manager.storage import (
            SQLSubscriptionStorage,
        )

        mock_session = MagicMock()
        manager = StorageManager()

        with patch.object(
            manager, "_get_current_session", return_value=mock_session
        ):
            subs = manager.subscriptions
            assert isinstance(subs, SQLSubscriptionStorage)

    def test_subscriptions_without_session_raises_runtime_error(self):
        """Raises RuntimeError when no session available."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        with patch.object(manager, "_get_current_session", return_value=None):
            with pytest.raises(RuntimeError):
                _ = manager.subscriptions


class TestStorageManagerRatingsProperty:
    """Tests for ratings property accessor."""

    def test_ratings_with_session_returns_sql_storage(self):
        """Returns SQLRatingStorage when session available."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.rating_system.storage import (
            SQLRatingStorage,
        )

        mock_session = MagicMock()
        manager = StorageManager()

        with patch.object(
            manager, "_get_current_session", return_value=mock_session
        ):
            ratings = manager.ratings
            assert isinstance(ratings, SQLRatingStorage)

    def test_ratings_without_session_raises_runtime_error(self):
        """Raises RuntimeError when no session available."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        with patch.object(manager, "_get_current_session", return_value=None):
            with pytest.raises(RuntimeError):
                _ = manager.ratings


class TestStorageManagerPreferencesProperty:
    """Tests for preferences property accessor."""

    def test_preferences_with_session_returns_sql_storage(self):
        """Returns SQLPreferenceStorage when session available."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.preference_manager.storage import (
            SQLPreferenceStorage,
        )

        mock_session = MagicMock()
        manager = StorageManager()

        with patch.object(
            manager, "_get_current_session", return_value=mock_session
        ):
            prefs = manager.preferences
            assert isinstance(prefs, SQLPreferenceStorage)


class TestStorageManagerGetUserFeed:
    """Tests for get_user_feed method."""

    def test_get_user_feed_returns_list(self):
        """Returns a list type."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        # Mock the storage properties
        mock_prefs = MagicMock()
        mock_prefs.get.return_value = None

        mock_cards = MagicMock()
        mock_cards.list.return_value = []

        manager._preferences = mock_prefs
        manager._cards = mock_cards
        manager.relevance_service = MagicMock()
        manager.relevance_service.personalize_feed.return_value = []

        # Mock get_current_session to return a valid session
        with patch.object(
            manager, "_get_current_session", return_value=MagicMock()
        ):
            with patch.object(
                type(manager),
                "preferences",
                new_callable=lambda: property(lambda self: mock_prefs),
            ):
                with patch.object(
                    type(manager),
                    "cards",
                    new_callable=lambda: property(lambda self: mock_cards),
                ):
                    result = manager.get_user_feed("user123")
                    assert isinstance(result, list)

    def test_get_user_feed_handles_exception(self):
        """Handles exceptions gracefully."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        # This tests exception handling - feed should return empty on error
        result = manager.get_user_feed("user123")
        assert result == []

    def test_get_user_feed_method_exists(self):
        """get_user_feed method exists with correct signature."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        manager = StorageManager()
        assert hasattr(manager, "get_user_feed")

        sig = inspect.signature(manager.get_user_feed)
        params = list(sig.parameters.keys())
        assert "user_id" in params
        assert "limit" in params
        assert "offset" in params

    def test_get_user_feed_default_limit(self):
        """Has default limit of 20."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        sig = inspect.signature(StorageManager.get_user_feed)
        params = sig.parameters

        assert params["limit"].default == 20

    def test_get_user_feed_default_offset(self):
        """Has default offset of 0."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        sig = inspect.signature(StorageManager.get_user_feed)
        params = sig.parameters

        assert params["offset"].default == 0


class TestStorageManagerGetTrendingNews:
    """Tests for get_trending_news method."""

    def test_get_trending_news_default_params(self):
        """Uses default parameters when not provided."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()
        manager.relevance_service = MagicMock()
        manager.relevance_service.filter_trending.return_value = []

        with patch.object(CardFactory, "get_recent_cards", return_value=[]):
            manager.get_trending_news()
            CardFactory.get_recent_cards.assert_called_once_with(
                hours=24,
                card_types=["news"],
                limit=20,  # 10 * 2
            )

    def test_get_trending_news_custom_params(self):
        """Uses custom parameters when provided."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()
        manager.relevance_service = MagicMock()
        manager.relevance_service.filter_trending.return_value = []

        with patch.object(CardFactory, "get_recent_cards", return_value=[]):
            manager.get_trending_news(hours=48, limit=5, min_impact=8)
            CardFactory.get_recent_cards.assert_called_once_with(
                hours=48, card_types=["news"], limit=10
            )
            manager.relevance_service.filter_trending.assert_called_once()

    def test_get_trending_news_exception_returns_empty(self):
        """Returns empty list on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        with patch.object(
            CardFactory,
            "get_recent_cards",
            side_effect=Exception("Error"),
        ):
            result = manager.get_trending_news()
            assert result == []


class TestStorageManagerRecordInteraction:
    """Tests for record_interaction method."""

    def test_record_view_interaction(self):
        """Records view interaction correctly."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        mock_card = MagicMock()
        mock_card.interaction = {}

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            with patch.object(CardFactory, "update_card", return_value=True):
                result = manager.record_interaction(
                    "user123", "card-123", InteractionType.VIEW
                )

                assert result is True
                assert mock_card.interaction["viewed"] is True
                assert "last_viewed" in mock_card.interaction
                assert mock_card.interaction["views"] == 1

    def test_record_view_increments_count(self):
        """View count is incremented."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        mock_card = MagicMock()
        mock_card.interaction = {"views": 5}

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            with patch.object(CardFactory, "update_card", return_value=True):
                manager.record_interaction(
                    "user123", "card-123", InteractionType.VIEW
                )

                assert mock_card.interaction["views"] == 6

    def test_record_vote_up_sets_flag(self):
        """Vote up sets the voted flag to 'up'."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        mock_card = MagicMock()
        mock_card.interaction = {}

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            with patch.object(CardFactory, "update_card", return_value=True):
                # Mock ratings to avoid property access error
                manager._ratings = MagicMock()
                with patch.object(
                    manager, "_get_current_session", return_value=MagicMock()
                ):
                    with patch.object(
                        type(manager),
                        "ratings",
                        new_callable=lambda: property(lambda self: MagicMock()),
                    ):
                        manager.record_interaction(
                            "user123", "card-123", InteractionType.VOTE_UP
                        )
                        # Verify interaction was updated
                        assert mock_card.interaction["voted"] == "up"

    def test_record_vote_down_sets_flag(self):
        """Vote down sets the voted flag to 'down'."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        mock_card = MagicMock()
        mock_card.interaction = {}

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            with patch.object(CardFactory, "update_card", return_value=True):
                with patch.object(
                    manager, "_get_current_session", return_value=MagicMock()
                ):
                    with patch.object(
                        type(manager),
                        "ratings",
                        new_callable=lambda: property(lambda self: MagicMock()),
                    ):
                        manager.record_interaction(
                            "user123", "card-123", InteractionType.VOTE_DOWN
                        )
                        assert mock_card.interaction["voted"] == "down"

    def test_record_research_interaction(self):
        """Records research interaction correctly."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        mock_card = MagicMock()
        mock_card.interaction = {}

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            with patch.object(CardFactory, "update_card", return_value=True):
                manager.record_interaction(
                    "user123", "card-123", InteractionType.RESEARCH
                )

                assert mock_card.interaction["researched"] is True
                assert mock_card.interaction["research_count"] == 1

    def test_record_interaction_with_metadata(self):
        """Metadata is stored with interaction."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        mock_card = MagicMock()
        mock_card.interaction = {}

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            with patch.object(CardFactory, "update_card", return_value=True):
                manager.record_interaction(
                    "user123",
                    "card-123",
                    InteractionType.VIEW,
                    metadata={"source": "mobile"},
                )

                assert mock_card.interaction[
                    "InteractionType.VIEW_metadata"
                ] == {"source": "mobile"}

    def test_record_interaction_card_not_found(self):
        """Returns False when card not found."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        with patch.object(CardFactory, "load_card", return_value=None):
            result = manager.record_interaction(
                "user123", "nonexistent", InteractionType.VIEW
            )
            assert result is False

    def test_record_interaction_exception_returns_false(self):
        """Returns False on exception."""
        from local_deep_research.news.core.storage_manager import (
            StorageManager,
            InteractionType,
        )
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        with patch.object(
            CardFactory, "load_card", side_effect=Exception("Error")
        ):
            result = manager.record_interaction(
                "user123", "card-123", InteractionType.VIEW
            )
            assert result is False


class TestStorageManagerGetUserSubscriptions:
    """Tests for get_user_subscriptions method."""

    def test_get_user_subscriptions_method_exists(self):
        """Method exists with correct signature."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        manager = StorageManager()
        assert hasattr(manager, "get_user_subscriptions")

        sig = inspect.signature(manager.get_user_subscriptions)
        params = list(sig.parameters.keys())
        assert "user_id" in params

    def test_get_user_subscriptions_exception_returns_empty(self):
        """Returns empty list on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        # Without proper session setup, should return empty list
        result = manager.get_user_subscriptions("user123")
        assert result == []


class TestStorageManagerGetUserStats:
    """Tests for get_user_stats method."""

    def test_get_user_stats_method_exists(self):
        """Method exists with correct signature."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        manager = StorageManager()
        assert hasattr(manager, "get_user_stats")

        sig = inspect.signature(manager.get_user_stats)
        params = list(sig.parameters.keys())
        assert "user_id" in params

    def test_get_user_stats_exception_returns_empty(self):
        """Returns empty dict on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        # Without proper session setup, should return empty dict
        result = manager.get_user_stats("user123")
        assert result == {}


class TestStorageManagerGetCard:
    """Tests for get_card method."""

    def test_get_card_success(self):
        """Returns card when found."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()
        mock_card = MagicMock()

        with patch.object(CardFactory, "load_card", return_value=mock_card):
            result = manager.get_card("card-123")
            assert result is mock_card

    def test_get_card_not_found(self):
        """Returns None when card not found."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        with patch.object(CardFactory, "load_card", return_value=None):
            result = manager.get_card("nonexistent")
            assert result is None

    def test_get_card_exception_returns_none(self):
        """Returns None on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()

        with patch.object(
            CardFactory, "load_card", side_effect=Exception("Error")
        ):
            result = manager.get_card("card-123")
            assert result is None


class TestStorageManagerGetCardInteractions:
    """Tests for get_card_interactions method."""

    def test_get_card_interactions_method_exists(self):
        """Method exists with correct signature."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        manager = StorageManager()
        assert hasattr(manager, "get_card_interactions")

        sig = inspect.signature(manager.get_card_interactions)
        params = list(sig.parameters.keys())
        assert "card_id" in params

    def test_get_card_interactions_exception_returns_empty(self):
        """Returns empty list on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        # Without proper session setup, should return empty list
        result = manager.get_card_interactions("card-123")
        assert result == []


class TestStorageManagerUpdateCard:
    """Tests for update_card method."""

    def test_update_card_success(self):
        """Returns True on successful update."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()
        mock_card = MagicMock()

        with patch.object(CardFactory, "update_card", return_value=True):
            result = manager.update_card(mock_card)
            assert result is True

    def test_update_card_exception_returns_false(self):
        """Returns False on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager
        from local_deep_research.news.core.card_factory import CardFactory

        manager = StorageManager()
        mock_card = MagicMock()

        with patch.object(
            CardFactory, "update_card", side_effect=Exception("Error")
        ):
            result = manager.update_card(mock_card)
            assert result is False


class TestStorageManagerCleanupOldData:
    """Tests for cleanup_old_data method."""

    def test_cleanup_old_data_method_exists(self):
        """Method exists with correct signature."""
        from local_deep_research.news.core.storage_manager import StorageManager
        import inspect

        manager = StorageManager()
        assert hasattr(manager, "cleanup_old_data")

        sig = inspect.signature(manager.cleanup_old_data)
        params = sig.parameters

        assert "days" in params
        assert params["days"].default == 30

    def test_cleanup_old_data_exception_returns_empty(self):
        """Returns empty dict on exception."""
        from local_deep_research.news.core.storage_manager import StorageManager

        manager = StorageManager()

        # Without proper session setup, should return empty dict
        result = manager.cleanup_old_data()
        assert result == {}


class TestGetStorageManagerSingleton:
    """Tests for get_storage_manager singleton function."""

    def test_get_storage_manager_returns_instance(self):
        """Returns StorageManager instance."""
        from local_deep_research.news.core.storage_manager import (
            get_storage_manager,
            StorageManager,
        )

        result = get_storage_manager()
        assert isinstance(result, StorageManager)

    def test_get_storage_manager_returns_same_instance(self):
        """Returns same instance on subsequent calls."""
        from local_deep_research.news.core.storage_manager import (
            get_storage_manager,
        )

        result1 = get_storage_manager()
        result2 = get_storage_manager()
        assert result1 is result2
