"""
Deep behavioral tests for base_preference.py pure logic.
Tests default preferences, interest management, topic registry,
and source boosting patterns.
"""

from datetime import datetime, timedelta, timezone


# --- Default preferences structure ---


class TestDefaultPreferencesStructure:
    """Tests for get_default_preferences structure."""

    def _get_default_preferences(self):
        return {
            "liked_categories": [],
            "disliked_categories": [],
            "liked_topics": [],
            "disliked_topics": [],
            "interests": {},
            "source_weights": {},
            "impact_threshold": 5,
            "focus_preferences": {
                "surprising": False,
                "breaking": True,
                "positive": False,
                "local": False,
            },
            "custom_search_terms": "",
            "search_strategy": "news_aggregation",
            "created_at": "2025-06-15T10:00:00+00:00",
            "preferences_updated_at": "2025-06-15T10:00:00+00:00",
        }

    def test_has_liked_categories(self):
        prefs = self._get_default_preferences()
        assert "liked_categories" in prefs
        assert prefs["liked_categories"] == []

    def test_has_disliked_categories(self):
        prefs = self._get_default_preferences()
        assert "disliked_categories" in prefs
        assert prefs["disliked_categories"] == []

    def test_has_liked_topics(self):
        prefs = self._get_default_preferences()
        assert "liked_topics" in prefs

    def test_has_disliked_topics(self):
        prefs = self._get_default_preferences()
        assert "disliked_topics" in prefs

    def test_has_interests(self):
        prefs = self._get_default_preferences()
        assert "interests" in prefs
        assert prefs["interests"] == {}

    def test_has_source_weights(self):
        prefs = self._get_default_preferences()
        assert "source_weights" in prefs
        assert prefs["source_weights"] == {}

    def test_default_impact_threshold(self):
        prefs = self._get_default_preferences()
        assert prefs["impact_threshold"] == 5

    def test_has_focus_preferences(self):
        prefs = self._get_default_preferences()
        assert "focus_preferences" in prefs

    def test_default_search_strategy(self):
        prefs = self._get_default_preferences()
        assert prefs["search_strategy"] == "news_aggregation"


# --- Focus preferences structure ---


class TestFocusPreferencesStructure:
    """Tests for focus_preferences defaults."""

    def _get_default_focus(self):
        return {
            "surprising": False,
            "breaking": True,
            "positive": False,
            "local": False,
        }

    def test_surprising_default_false(self):
        focus = self._get_default_focus()
        assert focus["surprising"] is False

    def test_breaking_default_true(self):
        focus = self._get_default_focus()
        assert focus["breaking"] is True

    def test_positive_default_false(self):
        focus = self._get_default_focus()
        assert focus["positive"] is False

    def test_local_default_false(self):
        focus = self._get_default_focus()
        assert focus["local"] is False


# --- Add interest logic ---


class TestAddInterestLogic:
    """Tests for add_interest method logic."""

    def _add_interest(self, prefs, interest, weight=1.0):
        if "interests" not in prefs:
            prefs["interests"] = {}
        prefs["interests"][interest] = weight
        prefs["interests_updated_at"] = "2025-06-15T10:00:00+00:00"
        return prefs

    def test_adds_interest(self):
        prefs = {}
        result = self._add_interest(prefs, "AI")
        assert "AI" in result["interests"]

    def test_default_weight_1(self):
        prefs = {}
        result = self._add_interest(prefs, "AI")
        assert result["interests"]["AI"] == 1.0

    def test_custom_weight(self):
        prefs = {}
        result = self._add_interest(prefs, "AI", 2.0)
        assert result["interests"]["AI"] == 2.0

    def test_updates_timestamp(self):
        prefs = {}
        result = self._add_interest(prefs, "AI")
        assert "interests_updated_at" in result

    def test_creates_interests_dict(self):
        prefs = {}
        result = self._add_interest(prefs, "AI")
        assert "interests" in result


# --- Remove interest logic ---


class TestRemoveInterestLogic:
    """Tests for remove_interest method logic."""

    def _remove_interest(self, prefs, interest):
        if "interests" in prefs and interest in prefs["interests"]:
            del prefs["interests"][interest]
            prefs["interests_updated_at"] = "2025-06-15T10:00:00+00:00"
        return prefs

    def test_removes_interest(self):
        prefs = {"interests": {"AI": 1.0, "ML": 1.0}}
        result = self._remove_interest(prefs, "AI")
        assert "AI" not in result["interests"]
        assert "ML" in result["interests"]

    def test_no_error_if_not_exists(self):
        prefs = {"interests": {}}
        result = self._remove_interest(prefs, "AI")
        assert result == {"interests": {}}

    def test_no_error_if_no_interests_key(self):
        prefs = {}
        result = self._remove_interest(prefs, "AI")
        assert result == {}


# --- Ignore topic logic ---


class TestIgnoreTopicLogic:
    """Tests for ignore_topic method logic."""

    def _ignore_topic(self, prefs, topic):
        if "disliked_topics" not in prefs:
            prefs["disliked_topics"] = []
        if topic not in prefs["disliked_topics"]:
            prefs["disliked_topics"].append(topic)
            prefs["preferences_updated_at"] = "2025-06-15T10:00:00+00:00"
        return prefs

    def test_adds_topic(self):
        prefs = {}
        result = self._ignore_topic(prefs, "Politics")
        assert "Politics" in result["disliked_topics"]

    def test_no_duplicate(self):
        prefs = {"disliked_topics": ["Politics"]}
        result = self._ignore_topic(prefs, "Politics")
        assert result["disliked_topics"].count("Politics") == 1

    def test_creates_disliked_topics_list(self):
        prefs = {}
        result = self._ignore_topic(prefs, "Sports")
        assert "disliked_topics" in result

    def test_updates_timestamp_only_on_add(self):
        prefs = {"disliked_topics": ["Politics"]}
        result = self._ignore_topic(prefs, "Politics")
        assert "preferences_updated_at" not in result


# --- Boost source logic ---


class TestBoostSourceLogic:
    """Tests for boost_source method logic."""

    def _boost_source(self, prefs, source, weight=1.5):
        if "source_weights" not in prefs:
            prefs["source_weights"] = {}
        prefs["source_weights"][source] = weight
        prefs["preferences_updated_at"] = "2025-06-15T10:00:00+00:00"
        return prefs

    def test_adds_source(self):
        prefs = {}
        result = self._boost_source(prefs, "bbc.com")
        assert "bbc.com" in result["source_weights"]

    def test_default_weight_1_5(self):
        prefs = {}
        result = self._boost_source(prefs, "bbc.com")
        assert result["source_weights"]["bbc.com"] == 1.5

    def test_custom_weight(self):
        prefs = {}
        result = self._boost_source(prefs, "bbc.com", 2.0)
        assert result["source_weights"]["bbc.com"] == 2.0

    def test_overwrites_existing(self):
        prefs = {"source_weights": {"bbc.com": 1.0}}
        result = self._boost_source(prefs, "bbc.com", 2.0)
        assert result["source_weights"]["bbc.com"] == 2.0


# --- Topic Registry ---


class TestTopicRegistryStructure:
    """Tests for TopicRegistry topic data structure."""

    def _create_topic_entry(self, now):
        return {
            "first_seen": now,
            "count": 0,
            "last_seen": now,
        }

    def test_has_first_seen(self):
        now = datetime.now(timezone.utc)
        entry = self._create_topic_entry(now)
        assert entry["first_seen"] == now

    def test_has_count(self):
        now = datetime.now(timezone.utc)
        entry = self._create_topic_entry(now)
        assert entry["count"] == 0

    def test_has_last_seen(self):
        now = datetime.now(timezone.utc)
        entry = self._create_topic_entry(now)
        assert entry["last_seen"] == now


# --- Topic registration ---


class TestTopicRegistration:
    """Tests for register_topic logic."""

    def _register_topic(self, topics, topic, now):
        if topic not in topics:
            topics[topic] = {
                "first_seen": now,
                "count": 0,
                "last_seen": now,
            }
        topics[topic]["count"] += 1
        topics[topic]["last_seen"] = now
        return topics

    def test_new_topic(self):
        topics = {}
        now = datetime.now(timezone.utc)
        result = self._register_topic(topics, "AI", now)
        assert "AI" in result
        assert result["AI"]["count"] == 1

    def test_existing_topic_increments(self):
        now = datetime.now(timezone.utc)
        topics = {"AI": {"first_seen": now, "count": 5, "last_seen": now}}
        result = self._register_topic(topics, "AI", now)
        assert result["AI"]["count"] == 6

    def test_updates_last_seen(self):
        old_time = datetime(2025, 1, 1, tzinfo=timezone.utc)
        new_time = datetime(2025, 6, 15, tzinfo=timezone.utc)
        topics = {
            "AI": {"first_seen": old_time, "count": 1, "last_seen": old_time}
        }
        result = self._register_topic(topics, "AI", new_time)
        assert result["AI"]["last_seen"] == new_time

    def test_preserves_first_seen(self):
        old_time = datetime(2025, 1, 1, tzinfo=timezone.utc)
        new_time = datetime(2025, 6, 15, tzinfo=timezone.utc)
        topics = {
            "AI": {"first_seen": old_time, "count": 1, "last_seen": old_time}
        }
        result = self._register_topic(topics, "AI", new_time)
        assert result["AI"]["first_seen"] == old_time


# --- Get trending topics ---


class TestGetTrendingTopics:
    """Tests for get_trending_topics logic."""

    def _get_trending(self, topics, cutoff_time, limit):
        recent = [
            (topic, data)
            for topic, data in topics.items()
            if data["last_seen"] >= cutoff_time
        ]
        recent.sort(key=lambda x: x[1]["count"], reverse=True)
        return [topic for topic, _ in recent[:limit]]

    def test_filters_old_topics(self):
        cutoff = datetime(2025, 6, 1, tzinfo=timezone.utc)
        topics = {
            "AI": {
                "last_seen": datetime(2025, 6, 15, tzinfo=timezone.utc),
                "count": 10,
            },
            "Old": {
                "last_seen": datetime(2025, 1, 1, tzinfo=timezone.utc),
                "count": 5,
            },
        }
        result = self._get_trending(topics, cutoff, 10)
        assert "AI" in result
        assert "Old" not in result

    def test_sorts_by_count(self):
        cutoff = datetime(2025, 6, 1, tzinfo=timezone.utc)
        recent = datetime(2025, 6, 15, tzinfo=timezone.utc)
        topics = {
            "A": {"last_seen": recent, "count": 5},
            "B": {"last_seen": recent, "count": 10},
            "C": {"last_seen": recent, "count": 3},
        }
        result = self._get_trending(topics, cutoff, 10)
        assert result[0] == "B"
        assert result[1] == "A"
        assert result[2] == "C"

    def test_respects_limit(self):
        cutoff = datetime(2025, 6, 1, tzinfo=timezone.utc)
        recent = datetime(2025, 6, 15, tzinfo=timezone.utc)
        topics = {f"T{i}": {"last_seen": recent, "count": i} for i in range(20)}
        result = self._get_trending(topics, cutoff, 5)
        assert len(result) == 5

    def test_empty_when_all_old(self):
        cutoff = datetime(2025, 6, 1, tzinfo=timezone.utc)
        topics = {
            "Old": {
                "last_seen": datetime(2025, 1, 1, tzinfo=timezone.utc),
                "count": 10,
            },
        }
        result = self._get_trending(topics, cutoff, 10)
        assert result == []


# --- Get topic info ---


class TestGetTopicInfo:
    """Tests for get_topic_info logic."""

    def _get_topic_info(self, topics, topic):
        return topics.get(topic)

    def test_found_topic(self):
        topics = {"AI": {"count": 10}}
        result = self._get_topic_info(topics, "AI")
        assert result["count"] == 10

    def test_not_found_topic(self):
        topics = {}
        result = self._get_topic_info(topics, "AI")
        assert result is None


# --- Cutoff time calculation ---


class TestCutoffTimeCalculation:
    """Tests for trending topic cutoff calculation."""

    def _calc_cutoff(self, now, hours):
        return now - timedelta(hours=hours)

    def test_24_hours(self):
        now = datetime(2025, 6, 15, 12, 0, tzinfo=timezone.utc)
        cutoff = self._calc_cutoff(now, 24)
        assert cutoff == datetime(2025, 6, 14, 12, 0, tzinfo=timezone.utc)

    def test_48_hours(self):
        now = datetime(2025, 6, 15, 12, 0, tzinfo=timezone.utc)
        cutoff = self._calc_cutoff(now, 48)
        assert cutoff == datetime(2025, 6, 13, 12, 0, tzinfo=timezone.utc)


# --- Extract topics from content ---


class TestExtractTopicsFromContent:
    """Tests for topic extraction behavior."""

    def test_max_topics_limit(self):
        max_topics = 5
        topics = ["T1", "T2", "T3", "T4", "T5", "T6", "T7"]
        limited = topics[:max_topics]
        assert len(limited) == 5

    def test_registers_discovered_topics(self):
        topics_registry = {}
        discovered = ["AI", "ML", "NLP"]

        for topic in discovered:
            if topic not in topics_registry:
                topics_registry[topic] = {"count": 0}
            topics_registry[topic]["count"] += 1

        assert len(topics_registry) == 3


# --- Preferences timestamp patterns ---


class TestPreferencesTimestampPatterns:
    """Tests for timestamp update patterns."""

    def test_created_at_set_once(self):
        prefs = {"created_at": "2025-01-01T00:00:00+00:00"}
        # created_at should not change
        assert prefs["created_at"] == "2025-01-01T00:00:00+00:00"

    def test_preferences_updated_at_changes(self):
        prefs = {"preferences_updated_at": "2025-01-01T00:00:00+00:00"}
        prefs["preferences_updated_at"] = "2025-06-15T10:00:00+00:00"
        assert prefs["preferences_updated_at"] == "2025-06-15T10:00:00+00:00"

    def test_interests_updated_at_changes(self):
        prefs = {}
        prefs["interests_updated_at"] = "2025-06-15T10:00:00+00:00"
        assert "interests_updated_at" in prefs
