"""
Deep behavioral tests for topic_subscription.py.
Tests TopicSubscription init, query generation, topic evolution,
activity tracking, auto-expiration, merging, and factory patterns.
"""

from unittest.mock import patch, MagicMock

# Mock SQLSubscriptionStorage before importing
with patch(
    "local_deep_research.news.subscription_manager.base_subscription.SQLSubscriptionStorage",
    return_value=MagicMock(),
):
    from local_deep_research.news.subscription_manager.topic_subscription import (
        TopicSubscription,
        TopicSubscriptionFactory,
    )

_storage_patch = patch(
    "local_deep_research.news.subscription_manager.base_subscription.SQLSubscriptionStorage",
    return_value=MagicMock(),
)
_storage_patch.start()


# --- TopicSubscription init ---


class TestTopicSubscriptionInit:
    """Tests for TopicSubscription initialization."""

    def test_stores_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.topic == "AI"

    def test_stores_user_id(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.user_id == "u1"

    def test_default_refresh_interval(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.refresh_interval_minutes == 240

    def test_custom_refresh_interval(self):
        sub = TopicSubscription(
            topic="AI", user_id="u1", refresh_interval_minutes=120
        )
        assert sub.refresh_interval_minutes == 120

    def test_subscription_type(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.subscription_type == "topic"

    def test_get_subscription_type(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.get_subscription_type() == "topic_subscription"

    def test_initial_topic_history(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.topic_history == ["AI"]

    def test_current_topic_matches(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.current_topic == "AI"

    def test_empty_related_topics(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.related_topics == []

    def test_custom_related_topics(self):
        sub = TopicSubscription(
            topic="AI", user_id="u1", related_topics=["ML", "DL"]
        )
        assert sub.related_topics == ["ML", "DL"]

    def test_has_id(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.id is not None

    def test_custom_subscription_id(self):
        sub = TopicSubscription(
            topic="AI", user_id="u1", subscription_id="custom-id"
        )
        assert sub.id == "custom-id"

    def test_default_activity_threshold(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.activity_threshold == 3

    def test_last_significant_activity_set(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.last_significant_activity is not None

    def test_metadata_subscription_type(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.metadata["subscription_type"] == "topic"

    def test_metadata_original_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.metadata["original_topic"] == "AI"

    def test_metadata_is_trending_false(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.metadata["is_trending"] is False

    def test_metadata_topic_category_none(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.metadata["topic_category"] is None

    def test_auto_creates_source(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.source is not None
        assert sub.source.type == "news_topic"

    def test_source_created_from(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert "AI" in sub.source.created_from

    def test_is_active_default(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        assert sub.is_active is True


# --- generate_search_query ---


class TestTopicSearchQuery:
    """Tests for topic search query generation."""

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_contains_topic(self, _):
        sub = TopicSubscription(topic="climate change", user_id="u1")
        query = sub.generate_search_query()
        assert "climate change" in query

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_has_news_terms(self, _):
        sub = TopicSubscription(topic="AI", user_id="u1")
        query = sub.generate_search_query()
        lower = query.lower()
        assert "news" in lower
        assert "latest" in lower

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_includes_related_topics(self, _):
        sub = TopicSubscription(
            topic="AI", user_id="u1", related_topics=["ML", "DL", "NLP"]
        )
        query = sub.generate_search_query()
        assert "ML" in query
        assert "DL" in query

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_max_2_related_topics(self, _):
        sub = TopicSubscription(
            topic="AI", user_id="u1", related_topics=["ML", "DL", "NLP"]
        )
        query = sub.generate_search_query()
        # NLP should NOT be in query (only first 2 related)
        assert "NLP" not in query

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_uses_or_joining(self, _):
        sub = TopicSubscription(topic="AI", user_id="u1", related_topics=["ML"])
        query = sub.generate_search_query()
        assert "OR" in query

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_quotes_around_parts(self, _):
        sub = TopicSubscription(topic="AI", user_id="u1")
        query = sub.generate_search_query()
        assert '"AI"' in query

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_no_related_topics(self, _):
        sub = TopicSubscription(topic="AI", user_id="u1")
        query = sub.generate_search_query()
        assert "OR" not in query

    @patch(
        "local_deep_research.news.core.utils.get_local_date_string",
        return_value="2025-06-15",
    )
    def test_replaces_date_placeholder(self, _):
        sub = TopicSubscription(topic="news YYYY-MM-DD", user_id="u1")
        query = sub.generate_search_query()
        assert "2025-06-15" in query
        assert "YYYY-MM-DD" not in query


# --- evolve_topic ---


class TestTopicEvolution:
    """Tests for topic evolution."""

    def test_evolve_changes_current(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("Artificial Intelligence")
        assert sub.current_topic == "Artificial Intelligence"

    def test_evolve_preserves_original(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("Artificial Intelligence")
        assert sub.topic == "AI"

    def test_evolve_adds_to_history(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("Artificial Intelligence")
        assert len(sub.topic_history) == 2
        assert sub.topic_history[-1] == "Artificial Intelligence"

    def test_evolve_metadata_updated(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("AGI", reason="topic shift")
        assert sub.metadata["last_evolution"]["from"] == "AI"
        assert sub.metadata["last_evolution"]["to"] == "AGI"
        assert sub.metadata["last_evolution"]["reason"] == "topic shift"

    def test_evolve_same_topic_no_change(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("AI")
        assert len(sub.topic_history) == 1

    def test_multiple_evolutions(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("ML")
        sub.evolve_topic("Deep Learning")
        assert len(sub.topic_history) == 3
        assert sub.current_topic == "Deep Learning"


# --- update_activity ---


class TestUpdateActivity:
    """Tests for activity tracking."""

    def test_significant_count_sets_trending(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.metadata["is_trending"] = False
        sub.update_activity(5)  # >= threshold (3)
        assert sub.metadata["is_trending"] is True

    def test_significant_news_flag_sets_trending(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.metadata["is_trending"] = False
        sub.update_activity(1, significant_news=True)
        assert sub.metadata["is_trending"] is True

    def test_below_threshold_keeps_trending_if_recent(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.metadata["is_trending"] = True
        sub.update_activity(1)  # below threshold, but activity is recent
        # Won't go stale immediately (within 72 hours)
        assert sub.metadata["is_trending"] is True

    def test_exactly_threshold(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.activity_threshold = 3
        sub.update_activity(3)
        assert sub.metadata["is_trending"] is True

    def test_zero_count_not_trending(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        # Activity just happened so within 72 hours
        sub.update_activity(0)
        # Still within 72 hours so trending stays as-is


# --- add_related_topic ---


class TestAddRelatedTopic:
    """Tests for adding related topics."""

    def test_adds_new_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.add_related_topic("ML")
        assert "ML" in sub.related_topics

    def test_no_duplicate(self):
        sub = TopicSubscription(topic="AI", user_id="u1", related_topics=["ML"])
        sub.add_related_topic("ML")
        assert sub.related_topics.count("ML") == 1

    def test_does_not_add_current_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.add_related_topic("AI")
        assert len(sub.related_topics) == 0

    def test_multiple_additions(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.add_related_topic("ML")
        sub.add_related_topic("DL")
        assert len(sub.related_topics) == 2


# --- merge_with ---


class TestMergeWith:
    """Tests for merging subscriptions."""

    def test_adds_other_topic_as_related(self):
        sub1 = TopicSubscription(topic="AI", user_id="u1")
        sub2 = TopicSubscription(topic="ML", user_id="u1")
        sub1.merge_with(sub2)
        assert "ML" in sub1.related_topics

    def test_merges_related_topics(self):
        sub1 = TopicSubscription(topic="AI", user_id="u1")
        sub2 = TopicSubscription(
            topic="ML", user_id="u1", related_topics=["DL", "NLP"]
        )
        sub1.merge_with(sub2)
        assert "DL" in sub1.related_topics
        assert "NLP" in sub1.related_topics

    def test_metadata_records_merge(self):
        sub1 = TopicSubscription(topic="AI", user_id="u1")
        sub2 = TopicSubscription(topic="ML", user_id="u1")
        sub1.merge_with(sub2)
        assert sub1.metadata["merged_from"]["topic"] == "ML"
        assert "subscription_id" in sub1.metadata["merged_from"]

    def test_no_duplicate_related_after_merge(self):
        sub1 = TopicSubscription(
            topic="AI", user_id="u1", related_topics=["DL"]
        )
        sub2 = TopicSubscription(
            topic="ML", user_id="u1", related_topics=["DL"]
        )
        sub1.merge_with(sub2)
        assert sub1.related_topics.count("DL") == 1


# --- should_auto_expire ---


class TestShouldAutoExpire:
    """Tests for auto-expiration logic."""

    def test_no_refresh_never_expires(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        # refresh_count = 0
        assert sub.should_auto_expire() is False

    def test_with_errors_never_expires(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.error_count = 1
        sub.refresh_count = 1
        assert sub.should_auto_expire() is False

    def test_active_recently_does_not_expire(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.error_count = 0
        sub.refresh_count = 5
        # last_significant_activity is just now
        assert sub.should_auto_expire() is False


# --- get_statistics ---


class TestTopicStatistics:
    """Tests for topic subscription statistics."""

    def test_has_original_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        stats = sub.get_statistics()
        assert stats["original_topic"] == "AI"

    def test_has_current_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        stats = sub.get_statistics()
        assert stats["current_topic"] == "AI"

    def test_evolution_count_zero(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        stats = sub.get_statistics()
        assert stats["evolution_count"] == 0

    def test_evolution_count_after_evolve(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        sub.evolve_topic("AGI")
        stats = sub.get_statistics()
        assert stats["evolution_count"] == 1

    def test_related_topics_count(self):
        sub = TopicSubscription(
            topic="AI", user_id="u1", related_topics=["ML", "DL"]
        )
        stats = sub.get_statistics()
        assert stats["related_topics_count"] == 2

    def test_is_trending(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        stats = sub.get_statistics()
        assert stats["is_trending"] is False

    def test_total_refreshes(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        stats = sub.get_statistics()
        assert stats["total_refreshes"] == 0

    def test_days_since_activity(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        stats = sub.get_statistics()
        assert stats["days_since_activity"] < 1  # just created


# --- to_dict ---


class TestTopicToDict:
    """Tests for topic subscription serialization."""

    def test_has_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        d = sub.to_dict()
        assert d["topic"] == "AI"

    def test_has_current_topic(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        d = sub.to_dict()
        assert d["current_topic"] == "AI"

    def test_has_related_topics(self):
        sub = TopicSubscription(topic="AI", user_id="u1", related_topics=["ML"])
        d = sub.to_dict()
        assert d["related_topics"] == ["ML"]

    def test_has_topic_history(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        d = sub.to_dict()
        assert d["topic_history"] == ["AI"]

    def test_has_last_significant_activity(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        d = sub.to_dict()
        assert "last_significant_activity" in d

    def test_has_statistics(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        d = sub.to_dict()
        assert "statistics" in d

    def test_inherits_base_fields(self):
        sub = TopicSubscription(topic="AI", user_id="u1")
        d = sub.to_dict()
        assert "id" in d
        assert "user_id" in d
        assert "type" in d


# --- TopicSubscriptionFactory ---


class TestTopicSubscriptionFactory:
    """Tests for TopicSubscriptionFactory."""

    def test_from_news_extraction(self):
        sub = TopicSubscriptionFactory.from_news_extraction(
            user_id="u1", topic="AI", source_news_id="news-123"
        )
        assert sub.topic == "AI"
        assert sub.user_id == "u1"

    def test_news_extraction_source_type(self):
        sub = TopicSubscriptionFactory.from_news_extraction(
            user_id="u1", topic="AI", source_news_id="news-123"
        )
        assert sub.source.type == "news_topic"

    def test_news_extraction_source_id(self):
        sub = TopicSubscriptionFactory.from_news_extraction(
            user_id="u1", topic="AI", source_news_id="news-123"
        )
        assert sub.source.source_id == "news-123"

    def test_news_extraction_with_related(self):
        sub = TopicSubscriptionFactory.from_news_extraction(
            user_id="u1",
            topic="AI",
            source_news_id="n1",
            related_topics=["ML", "DL"],
        )
        assert sub.related_topics == ["ML", "DL"]

    def test_from_user_interest(self):
        sub = TopicSubscriptionFactory.from_user_interest(
            user_id="u1", topic="Climate Change"
        )
        assert sub.topic == "Climate Change"

    def test_user_interest_source_type(self):
        sub = TopicSubscriptionFactory.from_user_interest(
            user_id="u1", topic="AI"
        )
        assert sub.source.type == "user_interest"

    def test_user_interest_created_from(self):
        sub = TopicSubscriptionFactory.from_user_interest(
            user_id="u1", topic="AI"
        )
        assert "AI" in sub.source.created_from

    def test_factory_creates_unique_ids(self):
        s1 = TopicSubscriptionFactory.from_user_interest(
            user_id="u1", topic="AI"
        )
        s2 = TopicSubscriptionFactory.from_user_interest(
            user_id="u1", topic="AI"
        )
        assert s1.id != s2.id
