"""
Deep behavioral tests for storage_manager.py pure logic.
Tests InteractionType enum, user feed filtering, interaction recording,
stats calculation, and cleanup logic.
"""

from datetime import datetime, timedelta, timezone


# --- InteractionType enum ---


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

    def test_view_value(self):
        assert "view" == "view"

    def test_vote_up_value(self):
        assert "vote_up" == "vote_up"

    def test_vote_down_value(self):
        assert "vote_down" == "vote_down"

    def test_research_value(self):
        assert "research" == "research"

    def test_share_value(self):
        assert "share" == "share"

    def test_all_types(self):
        types = ["view", "vote_up", "vote_down", "research", "share"]
        assert len(types) == 5


# --- Session retrieval pattern ---


class TestSessionRetrievalPattern:
    """Tests for _get_current_session logic."""

    def _has_session(self, has_context, has_session_attr, session):
        if has_context and has_session_attr and session:
            return True
        return False

    def test_all_conditions_met(self):
        assert self._has_session(True, True, "mock_session") is True

    def test_no_context(self):
        assert self._has_session(False, True, "mock") is False

    def test_no_session_attr(self):
        assert self._has_session(True, False, "mock") is False

    def test_session_none(self):
        assert self._has_session(True, True, None) is False


# --- User feed filter building ---


class TestUserFeedFilterBuilding:
    """Tests for get_user_feed filter building."""

    def _build_feed_filter(self, user_id, user_prefs, card_types):
        if user_prefs and user_prefs.get("liked_categories"):
            filters = {
                "user_id": user_id,
                "categories": user_prefs["liked_categories"],
            }
        else:
            filters = {"user_id": user_id}

        if card_types:
            filters["card_type"] = card_types

        return filters

    def test_basic_filter(self):
        filters = self._build_feed_filter("user1", None, None)
        assert filters["user_id"] == "user1"
        assert "categories" not in filters

    def test_with_categories(self):
        prefs = {"liked_categories": ["Tech", "Science"]}
        filters = self._build_feed_filter("user1", prefs, None)
        assert filters["categories"] == ["Tech", "Science"]

    def test_with_card_types(self):
        filters = self._build_feed_filter("user1", None, ["news", "research"])
        assert filters["card_type"] == ["news", "research"]

    def test_full_filter(self):
        prefs = {"liked_categories": ["Tech"]}
        filters = self._build_feed_filter("user1", prefs, ["news"])
        assert filters["user_id"] == "user1"
        assert filters["categories"] == ["Tech"]
        assert filters["card_type"] == ["news"]


# --- Feed limit calculation ---


class TestFeedLimitCalculation:
    """Tests for extra fetching for filtering."""

    def _calc_fetch_limit(self, limit, multiplier=2):
        return limit * multiplier

    def test_default_multiplier(self):
        assert self._calc_fetch_limit(20) == 40

    def test_custom_multiplier(self):
        assert self._calc_fetch_limit(10, 3) == 30


# --- Interaction update: view ---


class TestViewInteractionUpdate:
    """Tests for VIEW interaction recording."""

    def _update_view_interaction(self, interaction, now):
        interaction["viewed"] = True
        interaction["last_viewed"] = now
        interaction["views"] = interaction.get("views", 0) + 1
        return interaction

    def test_sets_viewed_true(self):
        interaction = {}
        now = datetime.now(timezone.utc)
        result = self._update_view_interaction(interaction, now)
        assert result["viewed"] is True

    def test_sets_last_viewed(self):
        interaction = {}
        now = datetime.now(timezone.utc)
        result = self._update_view_interaction(interaction, now)
        assert result["last_viewed"] == now

    def test_increments_views(self):
        interaction = {"views": 5}
        now = datetime.now(timezone.utc)
        result = self._update_view_interaction(interaction, now)
        assert result["views"] == 6

    def test_initial_views(self):
        interaction = {}
        now = datetime.now(timezone.utc)
        result = self._update_view_interaction(interaction, now)
        assert result["views"] == 1


# --- Interaction update: vote up ---


class TestVoteUpInteractionUpdate:
    """Tests for VOTE_UP interaction recording."""

    def _update_vote_up(self, interaction):
        interaction["voted"] = "up"
        interaction["votes_up"] = interaction.get("votes_up", 0) + 1
        return interaction

    def test_sets_voted_up(self):
        interaction = {}
        result = self._update_vote_up(interaction)
        assert result["voted"] == "up"

    def test_increments_votes_up(self):
        interaction = {"votes_up": 3}
        result = self._update_vote_up(interaction)
        assert result["votes_up"] == 4

    def test_initial_votes_up(self):
        interaction = {}
        result = self._update_vote_up(interaction)
        assert result["votes_up"] == 1


# --- Interaction update: vote down ---


class TestVoteDownInteractionUpdate:
    """Tests for VOTE_DOWN interaction recording."""

    def _update_vote_down(self, interaction):
        interaction["voted"] = "down"
        interaction["votes_down"] = interaction.get("votes_down", 0) + 1
        return interaction

    def test_sets_voted_down(self):
        interaction = {}
        result = self._update_vote_down(interaction)
        assert result["voted"] == "down"

    def test_increments_votes_down(self):
        interaction = {"votes_down": 2}
        result = self._update_vote_down(interaction)
        assert result["votes_down"] == 3


# --- Interaction update: research ---


class TestResearchInteractionUpdate:
    """Tests for RESEARCH interaction recording."""

    def _update_research(self, interaction):
        interaction["researched"] = True
        interaction["research_count"] = interaction.get("research_count", 0) + 1
        return interaction

    def test_sets_researched_true(self):
        interaction = {}
        result = self._update_research(interaction)
        assert result["researched"] is True

    def test_increments_research_count(self):
        interaction = {"research_count": 5}
        result = self._update_research(interaction)
        assert result["research_count"] == 6


# --- Metadata attachment ---


class TestMetadataAttachment:
    """Tests for attaching metadata to interaction."""

    def _attach_metadata(self, interaction, interaction_type, metadata):
        if metadata:
            interaction[f"{interaction_type}_metadata"] = metadata
        return interaction

    def test_attaches_metadata(self):
        interaction = {}
        metadata = {"source": "button"}
        result = self._attach_metadata(interaction, "view", metadata)
        assert result["view_metadata"]["source"] == "button"

    def test_no_metadata(self):
        interaction = {}
        result = self._attach_metadata(interaction, "view", None)
        assert "view_metadata" not in result


# --- User stats calculation ---


class TestUserStatsCalculation:
    """Tests for get_user_stats calculation."""

    def _calc_votes_up(self, ratings):
        return sum(1 for r in ratings if r.get("value", 0) > 0)

    def _calc_votes_down(self, ratings):
        return sum(1 for r in ratings if r.get("value", 0) < 0)

    def _calc_total_views(self, cards):
        return sum(c.get("interaction", {}).get("views", 0) for c in cards)

    def test_count_votes_up(self):
        ratings = [{"value": 1}, {"value": -1}, {"value": 1}, {"value": 1}]
        assert self._calc_votes_up(ratings) == 3

    def test_count_votes_down(self):
        ratings = [{"value": 1}, {"value": -1}, {"value": -1}]
        assert self._calc_votes_down(ratings) == 2

    def test_total_views(self):
        cards = [
            {"interaction": {"views": 10}},
            {"interaction": {"views": 5}},
            {"interaction": {}},
        ]
        assert self._calc_total_views(cards) == 15

    def test_empty_ratings(self):
        assert self._calc_votes_up([]) == 0
        assert self._calc_votes_down([]) == 0


class TestUserStatsStructure:
    """Tests for user stats dictionary structure."""

    def _build_user_stats(
        self,
        subscription_count,
        votes_up,
        votes_down,
        total_views,
        cards_count,
        member_since,
    ):
        return {
            "subscriptions": subscription_count,
            "votes_up": votes_up,
            "votes_down": votes_down,
            "total_views": total_views,
            "cards_created": cards_count,
            "member_since": member_since,
        }

    def test_has_all_fields(self):
        stats = self._build_user_stats(5, 10, 2, 100, 20, "2025-01-01")
        assert "subscriptions" in stats
        assert "votes_up" in stats
        assert "votes_down" in stats
        assert "total_views" in stats
        assert "cards_created" in stats
        assert "member_since" in stats


# --- Card interactions conversion ---


class TestCardInteractionsConversion:
    """Tests for get_card_interactions conversion."""

    def _convert_rating_to_interaction(self, rating):
        return {
            "user_id": rating.get("user_id"),
            "interaction_type": "vote",
            "interaction_data": {
                "vote": "up" if rating.get("value", 0) > 0 else "down"
            },
            "timestamp": rating.get("created_at"),
        }

    def test_positive_rating(self):
        rating = {"user_id": "u1", "value": 1, "created_at": "2025-06-15"}
        result = self._convert_rating_to_interaction(rating)
        assert result["interaction_data"]["vote"] == "up"

    def test_negative_rating(self):
        rating = {"user_id": "u1", "value": -1, "created_at": "2025-06-15"}
        result = self._convert_rating_to_interaction(rating)
        assert result["interaction_data"]["vote"] == "down"

    def test_zero_rating_is_down(self):
        rating = {"user_id": "u1", "value": 0}
        result = self._convert_rating_to_interaction(rating)
        assert result["interaction_data"]["vote"] == "down"


# --- Cleanup cutoff calculation ---


class TestCleanupCutoffCalculation:
    """Tests for cleanup_old_data cutoff calculation."""

    def _calc_cutoff(self, days):
        return datetime.now(timezone.utc) - timedelta(days=days)

    def test_30_days(self):
        cutoff = self._calc_cutoff(30)
        expected = datetime.now(timezone.utc) - timedelta(days=30)
        # Within a second
        assert abs((cutoff - expected).total_seconds()) < 1

    def test_7_days(self):
        cutoff = self._calc_cutoff(7)
        expected = datetime.now(timezone.utc) - timedelta(days=7)
        assert abs((cutoff - expected).total_seconds()) < 1


class TestCleanupCountsStructure:
    """Tests for cleanup counts dictionary."""

    def _build_cleanup_counts(self, cards_deleted, ratings_deleted):
        return {"cards": cards_deleted, "ratings": ratings_deleted}

    def test_has_cards_count(self):
        counts = self._build_cleanup_counts(10, 5)
        assert counts["cards"] == 10

    def test_has_ratings_count(self):
        counts = self._build_cleanup_counts(10, 5)
        assert counts["ratings"] == 5


# --- Singleton pattern ---


class TestStorageManagerSingleton:
    """Tests for singleton pattern."""

    def test_instance_initially_none(self):
        _storage_manager = None
        assert _storage_manager is None

    def test_creates_on_first_call(self):
        _storage_manager = None
        if _storage_manager is None:
            _storage_manager = {"initialized": True}
        assert _storage_manager is not None

    def test_reuses_on_second_call(self):
        _storage_manager = {"call": 1}
        if _storage_manager is None:
            _storage_manager = {"call": 2}
        assert _storage_manager["call"] == 1


# --- Rating save data structure ---


class TestRatingSaveDataStructure:
    """Tests for rating save data structure for votes."""

    def _build_rating_save_data(self, user_id, card_id, rating_type, value):
        return {
            "user_id": user_id,
            "card_id": card_id,
            "rating_type": rating_type,
            "value": value,
        }

    def test_vote_up_data(self):
        data = self._build_rating_save_data("u1", "c1", "relevance", 1)
        assert data["value"] == 1

    def test_vote_down_data(self):
        data = self._build_rating_save_data("u1", "c1", "relevance", -1)
        assert data["value"] == -1


# --- Trending news parameters ---


class TestTrendingNewsParameters:
    """Tests for get_trending_news default parameters."""

    def test_default_hours(self):
        hours = 24
        assert hours == 24

    def test_default_limit(self):
        limit = 10
        assert limit == 10

    def test_default_min_impact(self):
        min_impact = 7
        assert min_impact == 7


# --- Storage property error handling ---


class TestStoragePropertyErrorHandling:
    """Tests for storage property error handling."""

    def _get_storage_or_raise(self, session, cached_storage):
        if session:
            return "new_storage"
        if cached_storage is None:
            raise RuntimeError("No database session available for news storage")
        return cached_storage

    def test_with_session(self):
        result = self._get_storage_or_raise("mock_session", None)
        assert result == "new_storage"

    def test_with_cached(self):
        result = self._get_storage_or_raise(None, "cached")
        assert result == "cached"

    def test_no_session_no_cache_raises(self):
        try:
            self._get_storage_or_raise(None, None)
            assert False, "Should have raised RuntimeError"
        except RuntimeError as e:
            assert "No database session available" in str(e)
