"""
Tests for SearchSubscription and SearchSubscriptionFactory.

Tests cover:
- SearchSubscription initialization and configuration
- Query transformation for news-focused searches
- Query evolution over time
- Statistics tracking
- Dictionary serialization
- Factory methods for creating subscriptions
"""

import pytest
from unittest.mock import patch, MagicMock
from datetime import datetime

from local_deep_research.news.subscription_manager.search_subscription import (
    SearchSubscription,
    SearchSubscriptionFactory,
)
from local_deep_research.news.core.base_card import CardSource


@pytest.fixture
def mock_storage():
    """Mock the SQLSubscriptionStorage to avoid database dependencies."""
    with patch(
        "local_deep_research.news.subscription_manager.base_subscription.SQLSubscriptionStorage"
    ) as mock:
        mock_instance = MagicMock()
        mock.return_value = mock_instance
        yield mock_instance


@pytest.fixture
def mock_utc_now():
    """Mock utc_now to return consistent timestamps."""
    with patch(
        "local_deep_research.news.subscription_manager.base_subscription.utc_now"
    ) as mock:
        fixed_time = datetime(2024, 1, 15, 12, 0, 0)
        mock.return_value = fixed_time
        yield fixed_time


class TestSearchSubscriptionInit:
    """Tests for SearchSubscription initialization."""

    def test_basic_initialization(self, mock_storage):
        """Test basic subscription creation with required parameters."""
        sub = SearchSubscription(
            user_id="user_123",
            query="python async programming",
        )

        assert sub.user_id == "user_123"
        assert sub.original_query == "python async programming"
        assert sub.current_query == "python async programming"
        assert sub.transform_to_news_query is True
        assert sub.subscription_type == "search"
        assert sub.is_active is True

    def test_initialization_with_custom_source(self, mock_storage):
        """Test initialization with custom CardSource."""
        source = CardSource(
            type="custom_type",
            source_id="custom_123",
            created_from="Custom source",
        )
        sub = SearchSubscription(
            user_id="user_123",
            query="test query",
            source=source,
        )

        assert sub.source.type == "custom_type"
        assert sub.source.source_id == "custom_123"

    def test_initialization_creates_default_source(self, mock_storage):
        """Test that default source is created when not provided."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test query",
        )

        assert sub.source.type == "user_search"
        assert "Search subscription" in sub.source.created_from

    def test_initialization_with_custom_refresh_interval(self, mock_storage):
        """Test initialization with custom refresh interval."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test",
            refresh_interval_minutes=120,
        )

        assert sub.refresh_interval_minutes == 120

    def test_initialization_with_transform_disabled(self, mock_storage):
        """Test initialization with query transformation disabled."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test",
            transform_to_news_query=False,
        )

        assert sub.transform_to_news_query is False

    def test_query_history_initialized(self, mock_storage):
        """Test that query history is initialized with original query."""
        sub = SearchSubscription(
            user_id="user_123",
            query="initial query",
        )

        assert sub.query_history == ["initial query"]

    def test_metadata_contains_subscription_info(self, mock_storage):
        """Test that metadata is properly populated."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test query",
            transform_to_news_query=True,
        )

        assert sub.metadata["subscription_type"] == "search"
        assert sub.metadata["original_query"] == "test query"
        assert sub.metadata["transform_enabled"] is True


class TestSearchSubscriptionQueryProperty:
    """Tests for the query property."""

    def test_query_property_returns_original_query(self, mock_storage):
        """Test that query property returns original query."""
        sub = SearchSubscription(
            user_id="user_123",
            query="original query",
        )

        # Modify current_query
        sub.current_query = "evolved query"

        # Property should still return original
        assert sub.query == "original query"


class TestGetSubscriptionType:
    """Tests for get_subscription_type method."""

    def test_returns_search_subscription(self, mock_storage):
        """Test returns correct subscription type identifier."""
        sub = SearchSubscription(user_id="user_123", query="test")

        assert sub.get_subscription_type() == "search_subscription"


class TestGenerateSearchQuery:
    """Tests for generate_search_query method."""

    def test_generates_news_query_with_transform_enabled(self, mock_storage):
        """Test query transformation adds news context."""
        sub = SearchSubscription(
            user_id="user_123",
            query="climate change",
            transform_to_news_query=True,
        )

        with patch(
            "local_deep_research.news.core.utils.get_local_date_string"
        ) as mock_date:
            mock_date.return_value = "2024-01-15"
            result = sub.generate_search_query()

        assert "climate change" in result
        assert "news" in result.lower() or "developments" in result.lower()

    def test_returns_unmodified_query_when_transform_disabled(
        self, mock_storage
    ):
        """Test returns original query when transform is disabled."""
        sub = SearchSubscription(
            user_id="user_123",
            query="climate change",
            transform_to_news_query=False,
        )

        with patch(
            "local_deep_research.news.core.utils.get_local_date_string"
        ) as mock_date:
            mock_date.return_value = "2024-01-15"
            result = sub.generate_search_query()

        assert result == "climate change"

    def test_replaces_date_placeholder(self, mock_storage):
        """Test YYYY-MM-DD placeholder is replaced."""
        sub = SearchSubscription(
            user_id="user_123",
            query="news from YYYY-MM-DD",
            transform_to_news_query=False,
        )

        with patch(
            "local_deep_research.news.core.utils.get_local_date_string"
        ) as mock_date:
            mock_date.return_value = "2024-01-15"
            result = sub.generate_search_query()

        assert "2024-01-15" in result
        assert "YYYY-MM-DD" not in result


class TestTransformToNewsQuery:
    """Tests for _transform_to_news_query method."""

    def test_skips_transform_if_already_has_news_terms(self, mock_storage):
        """Test doesn't add news terms if already present."""
        sub = SearchSubscription(user_id="user_123", query="test")

        # Queries with news terms should not be modified
        assert (
            sub._transform_to_news_query("latest technology")
            == "latest technology"
        )
        assert sub._transform_to_news_query("today's news") == "today's news"
        assert (
            sub._transform_to_news_query("recent updates") == "recent updates"
        )

    def test_adds_updates_for_technical_queries(self, mock_storage):
        """Test adds 'updates' for how-to/tutorial queries."""
        sub = SearchSubscription(user_id="user_123", query="test")

        result = sub._transform_to_news_query("how to use python")
        assert "updates" in result.lower() or "developments" in result.lower()

        result = sub._transform_to_news_query("tutorial on react")
        assert "updates" in result.lower() or "developments" in result.lower()

    def test_adds_breaking_news_for_security_queries(self, mock_storage):
        """Test adds 'breaking news' for security queries."""
        sub = SearchSubscription(user_id="user_123", query="test")

        result = sub._transform_to_news_query("security vulnerability")
        assert "breaking" in result.lower() or "alerts" in result.lower()

        result = sub._transform_to_news_query("data breach")
        assert "breaking" in result.lower() or "alerts" in result.lower()

    def test_adds_latest_news_for_general_queries(self, mock_storage):
        """Test adds 'latest news' for general queries."""
        sub = SearchSubscription(user_id="user_123", query="test")

        result = sub._transform_to_news_query("artificial intelligence")
        assert "news" in result.lower() or "developments" in result.lower()


class TestEvolveQuery:
    """Tests for evolve_query method."""

    def test_evolve_query_with_new_terms(self, mock_storage):
        """Test query evolution adds new terms."""
        sub = SearchSubscription(
            user_id="user_123",
            query="python programming",
        )

        sub.evolve_query("machine learning")

        assert sub.current_query == "python programming machine learning"
        assert len(sub.query_history) == 2
        assert sub.query_history[1] == "python programming machine learning"

    def test_evolve_query_with_none_does_nothing(self, mock_storage):
        """Test evolve_query with None does nothing."""
        sub = SearchSubscription(
            user_id="user_123",
            query="original",
        )

        sub.evolve_query(None)

        assert sub.current_query == "original"
        assert len(sub.query_history) == 1

    def test_evolve_query_preserves_original(self, mock_storage):
        """Test evolution preserves original query."""
        sub = SearchSubscription(
            user_id="user_123",
            query="original query",
        )

        sub.evolve_query("new terms")

        assert sub.original_query == "original query"
        assert sub.query == "original query"


class TestGetStatistics:
    """Tests for get_statistics method."""

    def test_returns_statistics_dict(self, mock_storage):
        """Test returns proper statistics dictionary."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test query",
        )

        stats = sub.get_statistics()

        assert "original_query" in stats
        assert "current_query" in stats
        assert "query_evolution_count" in stats
        assert "total_refreshes" in stats
        assert "success_rate" in stats

    def test_statistics_track_query_evolution(self, mock_storage):
        """Test statistics track query evolution count."""
        sub = SearchSubscription(
            user_id="user_123",
            query="original",
        )

        sub.evolve_query("first evolution")
        sub.evolve_query("second evolution")

        stats = sub.get_statistics()

        assert stats["query_evolution_count"] == 2

    def test_statistics_success_rate_calculation(self, mock_storage):
        """Test success rate is calculated correctly."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test",
        )

        # Simulate some refreshes
        sub.refresh_count = 8
        sub.error_count = 2

        stats = sub.get_statistics()

        assert stats["success_rate"] == 0.8

    def test_statistics_success_rate_zero_when_no_activity(self, mock_storage):
        """Test success rate is 0 when no refresh activity."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test",
        )

        stats = sub.get_statistics()

        assert stats["success_rate"] == 0


class TestToDict:
    """Tests for to_dict method."""

    def test_includes_all_fields(self, mock_storage, mock_utc_now):
        """Test to_dict includes all required fields."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test query",
            subscription_id="sub_123",
        )

        result = sub.to_dict()

        assert "id" in result
        assert "original_query" in result
        assert "current_query" in result
        assert "transform_to_news_query" in result
        assert "query_history" in result
        assert "statistics" in result

    def test_to_dict_includes_inherited_fields(
        self, mock_storage, mock_utc_now
    ):
        """Test to_dict includes fields from parent class."""
        sub = SearchSubscription(
            user_id="user_123",
            query="test",
        )

        result = sub.to_dict()

        # From BaseSubscription
        assert "user_id" in result
        assert "source" in result
        assert "created_at" in result
        assert "is_active" in result


class TestSearchSubscriptionFactory:
    """Tests for SearchSubscriptionFactory."""

    def test_from_user_search_creates_subscription(self, mock_storage):
        """Test factory creates subscription from user search."""
        sub = SearchSubscriptionFactory.from_user_search(
            user_id="user_123",
            search_query="test search",
            search_result_id="result_456",
        )

        assert sub.user_id == "user_123"
        assert sub.original_query == "test search"
        assert sub.source.type == "user_search"
        assert sub.source.source_id == "result_456"

    def test_from_user_search_with_extra_kwargs(self, mock_storage):
        """Test factory passes through extra kwargs."""
        sub = SearchSubscriptionFactory.from_user_search(
            user_id="user_123",
            search_query="test",
            refresh_interval_minutes=120,
            transform_to_news_query=False,
        )

        assert sub.refresh_interval_minutes == 120
        assert sub.transform_to_news_query is False

    def test_from_recommendation_creates_subscription(self, mock_storage):
        """Test factory creates subscription from recommendation."""
        sub = SearchSubscriptionFactory.from_recommendation(
            user_id="user_123",
            recommended_query="recommended topic",
            recommendation_source="user_interests",
        )

        assert sub.user_id == "user_123"
        assert sub.original_query == "recommended topic"
        assert sub.source.type == "recommendation"
        assert "user_interests" in sub.source.created_from

    def test_from_recommendation_default_type_in_metadata(self, mock_storage):
        """Test default recommendation type is stored in metadata."""
        sub = SearchSubscriptionFactory.from_recommendation(
            user_id="user_123",
            recommended_query="test",
            recommendation_source="trending",
        )

        # Default recommendation type should be "topic_based"
        assert sub.source.metadata["recommendation_type"] == "topic_based"
