"""
Deep behavioral tests for api.py subscription management logic patterns.
Tests update_subscription field mapping, create_subscription defaults,
subscription dict format, and news feed construction patterns.
"""

from datetime import datetime, timezone, timedelta


# --- update_subscription field mapping ---


class TestUpdateSubscriptionFieldMapping:
    """Tests for the field-by-field update pattern in update_subscription."""

    def _apply_updates(self, existing, data):
        """Mirror the field update pattern from api.py update_subscription."""
        if "name" in data:
            existing["name"] = data["name"]
        if "query_or_topic" in data:
            existing["query_or_topic"] = data["query_or_topic"]
        if "subscription_type" in data:
            existing["subscription_type"] = data["subscription_type"]
        if "refresh_interval_minutes" in data:
            old_interval = existing.get("refresh_interval_minutes")
            existing["refresh_interval_minutes"] = data[
                "refresh_interval_minutes"
            ]
            if old_interval != existing["refresh_interval_minutes"]:
                existing["next_refresh_recalculated"] = True
        if "is_active" in data:
            existing["status"] = "active" if data["is_active"] else "paused"
        if "status" in data:
            existing["status"] = data["status"]
        if "folder_id" in data:
            existing["folder_id"] = data["folder_id"]
        if "model_provider" in data:
            existing["model_provider"] = data["model_provider"]
        if "model" in data:
            existing["model"] = data["model"]
        if "search_strategy" in data:
            existing["search_strategy"] = data["search_strategy"]
        if "custom_endpoint" in data:
            existing["custom_endpoint"] = data["custom_endpoint"]
        if "search_engine" in data:
            existing["search_engine"] = data["search_engine"]
        if "search_iterations" in data:
            existing["search_iterations"] = data["search_iterations"]
        if "questions_per_iteration" in data:
            existing["questions_per_iteration"] = data[
                "questions_per_iteration"
            ]
        return existing

    def test_update_name(self):
        existing = {"name": "old"}
        result = self._apply_updates(existing, {"name": "new"})
        assert result["name"] == "new"

    def test_update_query(self):
        existing = {"query_or_topic": "old query"}
        result = self._apply_updates(existing, {"query_or_topic": "new query"})
        assert result["query_or_topic"] == "new query"

    def test_update_is_active_true(self):
        existing = {"status": "paused"}
        result = self._apply_updates(existing, {"is_active": True})
        assert result["status"] == "active"

    def test_update_is_active_false(self):
        existing = {"status": "active"}
        result = self._apply_updates(existing, {"is_active": False})
        assert result["status"] == "paused"

    def test_update_status_directly(self):
        existing = {"status": "active"}
        result = self._apply_updates(existing, {"status": "expired"})
        assert result["status"] == "expired"

    def test_update_interval_recalculates(self):
        existing = {"refresh_interval_minutes": 60}
        result = self._apply_updates(
            existing, {"refresh_interval_minutes": 120}
        )
        assert result["refresh_interval_minutes"] == 120
        assert result.get("next_refresh_recalculated") is True

    def test_same_interval_no_recalculate(self):
        existing = {"refresh_interval_minutes": 60}
        result = self._apply_updates(existing, {"refresh_interval_minutes": 60})
        assert result.get("next_refresh_recalculated") is None

    def test_update_model_provider(self):
        existing = {"model_provider": "OLLAMA"}
        result = self._apply_updates(existing, {"model_provider": "openai"})
        assert result["model_provider"] == "openai"

    def test_update_model(self):
        existing = {"model": "llama3"}
        result = self._apply_updates(existing, {"model": "gpt-4"})
        assert result["model"] == "gpt-4"

    def test_update_search_engine(self):
        existing = {"search_engine": None}
        result = self._apply_updates(existing, {"search_engine": "google"})
        assert result["search_engine"] == "google"

    def test_update_search_iterations(self):
        existing = {"search_iterations": 3}
        result = self._apply_updates(existing, {"search_iterations": 5})
        assert result["search_iterations"] == 5

    def test_update_questions_per_iteration(self):
        existing = {"questions_per_iteration": 5}
        result = self._apply_updates(existing, {"questions_per_iteration": 10})
        assert result["questions_per_iteration"] == 10

    def test_update_folder_id(self):
        existing = {"folder_id": None}
        result = self._apply_updates(existing, {"folder_id": "folder-123"})
        assert result["folder_id"] == "folder-123"

    def test_update_custom_endpoint(self):
        existing = {"custom_endpoint": None}
        result = self._apply_updates(
            existing, {"custom_endpoint": "http://api.example.com"}
        )
        assert result["custom_endpoint"] == "http://api.example.com"

    def test_partial_update_preserves_other_fields(self):
        existing = {
            "name": "original",
            "query_or_topic": "test",
            "status": "active",
        }
        result = self._apply_updates(existing, {"name": "updated"})
        assert result["name"] == "updated"
        assert result["query_or_topic"] == "test"
        assert result["status"] == "active"

    def test_empty_update_changes_nothing(self):
        existing = {"name": "test", "status": "active"}
        result = self._apply_updates(existing, {})
        assert result == {"name": "test", "status": "active"}


# --- Subscription API format ---


class TestSubscriptionAPIFormat:
    """Tests for subscription dict formatting in get_subscription."""

    def _format_subscription(self, sub):
        """Mirror the subscription formatting from api.py get_subscription."""
        return {
            "id": sub["id"],
            "name": sub.get("name") or "",
            "query_or_topic": sub["query_or_topic"],
            "subscription_type": sub["subscription_type"],
            "refresh_interval_minutes": sub["refresh_interval_minutes"],
            "is_active": sub.get("status") == "active",
            "status": sub.get("status"),
            "folder_id": sub.get("folder_id"),
            "model_provider": sub.get("model_provider"),
            "model": sub.get("model"),
            "search_strategy": sub.get("search_strategy"),
            "custom_endpoint": sub.get("custom_endpoint"),
            "search_engine": sub.get("search_engine"),
            "search_iterations": sub.get("search_iterations") or 3,
            "questions_per_iteration": sub.get("questions_per_iteration") or 5,
            "created_at": sub.get("created_at"),
            "updated_at": sub.get("updated_at"),
        }

    def test_is_active_from_status(self):
        sub = {
            "id": "1",
            "query_or_topic": "q",
            "subscription_type": "search",
            "refresh_interval_minutes": 60,
            "status": "active",
        }
        result = self._format_subscription(sub)
        assert result["is_active"] is True

    def test_inactive_from_paused(self):
        sub = {
            "id": "1",
            "query_or_topic": "q",
            "subscription_type": "search",
            "refresh_interval_minutes": 60,
            "status": "paused",
        }
        result = self._format_subscription(sub)
        assert result["is_active"] is False

    def test_name_fallback_empty(self):
        sub = {
            "id": "1",
            "query_or_topic": "q",
            "subscription_type": "search",
            "refresh_interval_minutes": 60,
            "name": None,
        }
        result = self._format_subscription(sub)
        assert result["name"] == ""

    def test_search_iterations_default_3(self):
        sub = {
            "id": "1",
            "query_or_topic": "q",
            "subscription_type": "search",
            "refresh_interval_minutes": 60,
            "search_iterations": None,
        }
        result = self._format_subscription(sub)
        assert result["search_iterations"] == 3

    def test_questions_per_iteration_default_5(self):
        sub = {
            "id": "1",
            "query_or_topic": "q",
            "subscription_type": "search",
            "refresh_interval_minutes": 60,
            "questions_per_iteration": None,
        }
        result = self._format_subscription(sub)
        assert result["questions_per_iteration"] == 5


# --- get_subscriptions list format ---


class TestGetSubscriptionsListFormat:
    """Tests for the subscription list format from get_subscriptions."""

    def _format_sub_list_item(self, sub, total_runs=0):
        """Mirror the subscription list formatting."""
        return {
            "id": sub["id"],
            "query": sub["query_or_topic"],
            "type": sub.get("subscription_type"),
            "refresh_minutes": sub.get("refresh_interval_minutes"),
            "created_at": sub.get("created_at"),
            "next_refresh": sub.get("next_refresh"),
            "last_refreshed": sub.get("last_refresh"),
            "is_active": sub.get("status") == "active",
            "total_runs": total_runs,
            "name": sub.get("name") or "",
            "folder_id": sub.get("folder_id"),
        }

    def test_query_maps_from_query_or_topic(self):
        sub = {"id": "1", "query_or_topic": "AI news", "status": "active"}
        result = self._format_sub_list_item(sub)
        assert result["query"] == "AI news"

    def test_total_runs_included(self):
        sub = {"id": "1", "query_or_topic": "test", "status": "active"}
        result = self._format_sub_list_item(sub, total_runs=42)
        assert result["total_runs"] == 42

    def test_is_active_true(self):
        sub = {"id": "1", "query_or_topic": "test", "status": "active"}
        result = self._format_sub_list_item(sub)
        assert result["is_active"] is True

    def test_like_pattern_for_subscription_id(self):
        """Test the LIKE pattern used to count research runs."""
        sub_id = "sub-abc-123"
        like_pattern = f'%"subscription_id": "{sub_id}"%'
        assert f'"subscription_id": "{sub_id}"' in like_pattern
        assert like_pattern.startswith("%")
        assert like_pattern.endswith("%")


# --- News feed item construction ---


class TestNewsFeedItemConstruction:
    """Tests for news_item dict construction in get_news_feed."""

    def _build_news_item(
        self,
        row,
        metadata,
        headline,
        summary,
        findings,
        category,
        topics,
        links,
    ):
        research_id = row.get("uuid_id") or str(row["id"])
        return {
            "id": f"news-{research_id}",
            "headline": headline,
            "category": category,
            "summary": summary or f"Research analysis for: {headline[:100]}",
            "findings": findings,
            "impact_score": metadata.get("impact_score", 0),
            "upvotes": metadata.get("upvotes", 0),
            "downvotes": metadata.get("downvotes", 0),
            "source_url": f"/results/{research_id}",
            "topics": topics,
            "links": links,
            "research_id": research_id,
            "original_query": row["query"],
            "is_news": metadata.get("is_news_search", False),
            "news_date": metadata.get("news_date"),
            "news_source": metadata.get("news_source"),
            "priority": metadata.get("priority", "normal"),
        }

    def test_id_has_news_prefix(self):
        row = {"id": "123", "uuid_id": "abc-123", "query": "test"}
        item = self._build_news_item(
            row, {}, "Headline", "", "", "Tech", [], []
        )
        assert item["id"] == "news-abc-123"

    def test_uses_uuid_id_when_available(self):
        row = {"id": 1, "uuid_id": "uuid-val", "query": "test"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["research_id"] == "uuid-val"

    def test_falls_back_to_str_id(self):
        row = {"id": 42, "uuid_id": None, "query": "test"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["research_id"] == "42"

    def test_impact_score_default_zero(self):
        row = {"id": "1", "uuid_id": "1", "query": "test"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["impact_score"] == 0

    def test_priority_default_normal(self):
        row = {"id": "1", "uuid_id": "1", "query": "test"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["priority"] == "normal"

    def test_summary_fallback(self):
        row = {"id": "1", "uuid_id": "1", "query": "test"}
        item = self._build_news_item(
            row, {}, "My Headline", "", "", "C", [], []
        )
        assert "Research analysis for: My Headline" in item["summary"]

    def test_source_url_format(self):
        row = {"id": "1", "uuid_id": "res-abc", "query": "test"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["source_url"] == "/results/res-abc"

    def test_is_news_flag(self):
        row = {"id": "1", "uuid_id": "1", "query": "test"}
        item = self._build_news_item(
            row, {"is_news_search": True}, "H", "", "", "C", [], []
        )
        assert item["is_news"] is True

    def test_is_news_default_false(self):
        row = {"id": "1", "uuid_id": "1", "query": "test"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["is_news"] is False

    def test_preserves_original_query(self):
        row = {"id": "1", "uuid_id": "1", "query": "original query text"}
        item = self._build_news_item(row, {}, "H", "", "", "C", [], [])
        assert item["original_query"] == "original query text"


# --- Headline determination logic ---


class TestHeadlineDetermination:
    """Tests for the headline determination logic in get_news_feed."""

    def _determine_headline(self, row_title, metadata, row_query):
        """Mirror headline determination from get_news_feed."""
        headline = row_title or metadata.get("generated_headline")
        if not headline and metadata.get("is_news_search"):
            subscription_name = metadata.get("subscription_name")
            if subscription_name:
                headline = f"News Update: {subscription_name}"
            else:
                headline = f"News: {row_query[:60]}..."
        return headline

    def test_uses_row_title_first(self):
        result = self._determine_headline(
            "Row Title", {"generated_headline": "Meta"}, "query"
        )
        assert result == "Row Title"

    def test_uses_metadata_headline_second(self):
        result = self._determine_headline(
            None, {"generated_headline": "Meta Headline"}, "query"
        )
        assert result == "Meta Headline"

    def test_news_search_uses_subscription_name(self):
        result = self._determine_headline(
            None,
            {"is_news_search": True, "subscription_name": "My Sub"},
            "query",
        )
        assert result == "News Update: My Sub"

    def test_news_search_fallback_to_query(self):
        result = self._determine_headline(
            None,
            {"is_news_search": True},
            "AI breakthroughs today",
        )
        assert result.startswith("News: AI")

    def test_no_headline_returns_none(self):
        result = self._determine_headline(None, {}, "query")
        assert result is None

    def test_skip_no_headline(self):
        headline = None
        skip = not headline or headline == "[No headline available]"
        assert skip is True

    def test_skip_placeholder_headline(self):
        headline = "[No headline available]"
        skip = not headline or headline == "[No headline available]"
        assert skip is True

    def test_dont_skip_valid_headline(self):
        headline = "Breaking News"
        skip = not headline or headline == "[No headline available]"
        assert skip is False


# --- Category and topics defaults ---


class TestCategoryAndTopicsDefaults:
    """Tests for category/topics default patterns."""

    def test_category_from_metadata(self):
        metadata = {"category": "Technology"}
        category = metadata.get("category")
        if not category:
            category = "[Uncategorized]"
        assert category == "Technology"

    def test_category_default_uncategorized(self):
        metadata = {}
        category = metadata.get("category")
        if not category:
            category = "[Uncategorized]"
        assert category == "[Uncategorized]"

    def test_topics_from_metadata(self):
        metadata = {"generated_topics": ["AI", "ML"]}
        topics = metadata.get("generated_topics")
        if not topics:
            topics = ["[No topics]"]
        assert topics == ["AI", "ML"]

    def test_topics_default_placeholder(self):
        metadata = {}
        topics = metadata.get("generated_topics")
        if not topics:
            topics = ["[No topics]"]
        assert topics == ["[No topics]"]


# --- Status filtering ---


class TestStatusFiltering:
    """Tests for status-based filtering in news feed."""

    def test_skip_in_progress(self):
        status = "in_progress"
        skip = status in ["in_progress", "suspended"]
        assert skip is True

    def test_skip_suspended(self):
        status = "suspended"
        skip = status in ["in_progress", "suspended"]
        assert skip is True

    def test_dont_skip_completed(self):
        status = "completed"
        skip = status in ["in_progress", "suspended"]
        assert skip is False

    def test_dont_skip_failed(self):
        status = "failed"
        skip = status in ["in_progress", "suspended"]
        assert skip is False


# --- Limit validation ---


class TestLimitValidation:
    """Tests for limit validation in get_news_feed."""

    def test_limit_zero_invalid(self):
        limit = 0
        assert limit < 1

    def test_limit_negative_invalid(self):
        limit = -5
        assert limit < 1

    def test_limit_one_valid(self):
        limit = 1
        assert limit >= 1

    def test_limit_large_valid(self):
        limit = 10000
        assert limit >= 1


# --- Next refresh calculation ---


class TestNextRefreshRecalculation:
    """Tests for next_refresh recalculation on interval change."""

    def test_new_interval_calculates_from_now(self):
        now = datetime.now(timezone.utc)
        new_minutes = 120
        next_refresh = now + timedelta(minutes=new_minutes)
        assert next_refresh > now

    def test_old_interval_differs_triggers_recalc(self):
        old = 60
        new = 120
        should_recalc = old != new
        assert should_recalc is True

    def test_same_interval_no_recalc(self):
        old = 60
        new = 60
        should_recalc = old != new
        assert should_recalc is False

    def test_recalc_from_last_refresh(self):
        last_refresh = datetime(2025, 6, 15, 12, 0, 0, tzinfo=timezone.utc)
        new_minutes = 30
        next_refresh = last_refresh + timedelta(minutes=new_minutes)
        assert next_refresh.minute == 30
