"""High-value tests for news/subscription_manager/base_subscription.py.

Covers BaseSubscription lifecycle: init, should_refresh, error handling,
exponential backoff, pause/resume, update_interval, and to_dict.
"""

import pytest
from unittest.mock import patch

from local_deep_research.news.subscription_manager.base_subscription import (
    BaseSubscription,
)
from local_deep_research.news.core.base_card import CardSource


class ConcreteSubscription(BaseSubscription):
    """Concrete implementation for testing."""

    def __init__(self, *args, **kwargs):
        with patch(
            "local_deep_research.news.subscription_manager.base_subscription.SQLSubscriptionStorage"
        ):
            super().__init__(*args, **kwargs)
        self.subscription_type = "test"

    def generate_search_query(self):
        return self.query_or_topic

    def get_subscription_type(self):
        return "test_subscription"


def _make_source():
    return CardSource(type="test", source_id="src1", created_from="manual")


class TestBaseSubscriptionInit:
    """Test initialization."""

    def test_stores_basic_fields(self):
        sub = ConcreteSubscription("user1", _make_source(), "test query")
        assert sub.user_id == "user1"
        assert sub.query_or_topic == "test query"
        assert sub.is_active is True

    def test_default_interval(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        assert sub.refresh_interval_minutes == 240

    def test_custom_interval(self):
        sub = ConcreteSubscription(
            "user1", _make_source(), "q", refresh_interval_minutes=120
        )
        assert sub.refresh_interval_minutes == 120

    def test_generates_id_when_not_provided(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        assert sub.id is not None and len(sub.id) > 0

    def test_uses_provided_id(self):
        sub = ConcreteSubscription(
            "user1", _make_source(), "q", subscription_id="custom-id"
        )
        assert sub.id == "custom-id"

    def test_initial_refresh_count_zero(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        assert sub.refresh_count == 0
        assert sub.error_count == 0
        assert sub.last_error is None


class TestShouldRefresh:
    """Test should_refresh logic."""

    def test_inactive_subscription_should_not_refresh(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.is_active = False
        assert sub.should_refresh() is False

    def test_is_due_for_refresh_alias(self):
        """is_due_for_refresh is an alias for should_refresh."""
        sub = ConcreteSubscription("user1", _make_source(), "q")
        assert sub.is_due_for_refresh() == sub.should_refresh()


class TestRefreshCallbacks:
    """Test on_refresh_start, on_refresh_success, on_refresh_error."""

    def test_on_refresh_start_sets_last_refreshed(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        assert sub.last_refreshed is None
        sub.on_refresh_start()
        assert sub.last_refreshed is not None

    def test_on_refresh_success_increments_count(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.on_refresh_start()
        sub.on_refresh_success(["result"])
        assert sub.refresh_count == 1

    def test_on_refresh_success_resets_errors(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.error_count = 3
        sub.on_refresh_start()
        sub.on_refresh_success(["result"])
        assert sub.error_count == 0

    def test_on_refresh_error_increments_error_count(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.on_refresh_error(RuntimeError("test error"))
        assert sub.error_count == 1
        assert sub.last_error == "test error"

    def test_on_refresh_error_exponential_backoff(self):
        """Error backoff increases next_refresh time exponentially."""
        sub = ConcreteSubscription("user1", _make_source(), "q")
        first_next = sub.next_refresh
        sub.on_refresh_error(RuntimeError("err"))
        after_first_error = sub.next_refresh
        # After error, next_refresh should be further in the future
        assert after_first_error > first_next

    def test_disables_after_10_errors(self):
        """Subscription disabled after 10 consecutive errors."""
        sub = ConcreteSubscription("user1", _make_source(), "q")
        for _ in range(10):
            sub.on_refresh_error(RuntimeError("err"))
        assert sub.is_active is False
        assert sub.error_count == 10


class TestPauseResume:
    """Test pause and resume."""

    def test_pause_deactivates(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.pause()
        assert sub.is_active is False

    def test_resume_activates_and_resets_errors(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.error_count = 5
        sub.is_active = False
        sub.resume()
        assert sub.is_active is True
        assert sub.error_count == 0


class TestUpdateInterval:
    """Test update_interval validation."""

    def test_valid_interval(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        sub.update_interval(120)
        assert sub.refresh_interval_minutes == 120

    def test_too_short_interval_raises(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        with pytest.raises(ValueError, match="at least 60"):
            sub.update_interval(30)

    def test_too_long_interval_raises(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        with pytest.raises(ValueError, match="30 days"):
            sub.update_interval(60 * 24 * 31)


class TestToDict:
    """Test to_dict serialization."""

    def test_has_expected_keys(self):
        sub = ConcreteSubscription("user1", _make_source(), "test query")
        d = sub.to_dict()
        expected_keys = {
            "id",
            "type",
            "user_id",
            "query_or_topic",
            "source",
            "created_at",
            "last_refreshed",
            "next_refresh",
            "refresh_interval_minutes",
            "is_active",
            "refresh_count",
            "error_count",
            "last_error",
            "metadata",
        }
        assert set(d.keys()) == expected_keys

    def test_last_refreshed_none_serialized(self):
        """last_refreshed=None serialized as None."""
        sub = ConcreteSubscription("user1", _make_source(), "q")
        assert sub.to_dict()["last_refreshed"] is None

    def test_source_dict_structure(self):
        sub = ConcreteSubscription("user1", _make_source(), "q")
        source_dict = sub.to_dict()["source"]
        assert "type" in source_dict
        assert "source_id" in source_dict
