"""
Deep behavioral tests for base_card.py pure logic.
Tests CardSource, CardVersion, BaseCard helper methods,
impact calculation, topic extraction, and card type conversions.
"""

from datetime import datetime, timezone


# --- CardSource structure ---


class TestCardSourceStructure:
    """Tests for CardSource dataclass structure."""

    def _create_source(
        self, type, source_id=None, created_from="", metadata=None
    ):
        return {
            "type": type,
            "source_id": source_id,
            "created_from": created_from,
            "metadata": metadata or {},
        }

    def test_minimal_source(self):
        source = self._create_source("news_item")
        assert source["type"] == "news_item"
        assert source["source_id"] is None

    def test_full_source(self):
        source = self._create_source(
            type="user_search",
            source_id="search-123",
            created_from="User search",
            metadata={"query": "AI news"},
        )
        assert source["type"] == "user_search"
        assert source["source_id"] == "search-123"
        assert source["metadata"]["query"] == "AI news"

    def test_default_metadata_empty(self):
        source = self._create_source("subscription")
        assert source["metadata"] == {}

    def test_source_types(self):
        valid_types = [
            "news_item",
            "user_search",
            "subscription",
            "news_research",
        ]
        for t in valid_types:
            source = self._create_source(t)
            assert source["type"] == t


# --- CardVersion structure ---


class TestCardVersionStructure:
    """Tests for CardVersion fields."""

    def _create_version(
        self, version_id, created_at, content, query, strategy=None
    ):
        return {
            "version_id": version_id or "auto-generated-id",
            "created_at": created_at,
            "content": content,
            "query_used": query,
            "search_strategy": strategy,
        }

    def test_basic_version(self):
        version = self._create_version(
            version_id="v-123",
            created_at=datetime.now(timezone.utc),
            content={"result": "data"},
            query="test query",
        )
        assert version["version_id"] == "v-123"
        assert version["query_used"] == "test query"

    def test_auto_generate_version_id(self):
        version = self._create_version(
            version_id=None,
            created_at=datetime.now(timezone.utc),
            content={},
            query="q",
        )
        assert version["version_id"] == "auto-generated-id"

    def test_optional_strategy(self):
        version = self._create_version(
            version_id="v1",
            created_at=datetime.now(timezone.utc),
            content={},
            query="q",
            strategy="focused_iteration",
        )
        assert version["search_strategy"] == "focused_iteration"


# --- Interaction structure ---


class TestInteractionStructure:
    """Tests for card interaction dictionary."""

    def _get_default_interaction(self):
        return {
            "votes_up": 0,
            "votes_down": 0,
            "views": 0,
            "shares": 0,
        }

    def test_default_values(self):
        interaction = self._get_default_interaction()
        assert interaction["votes_up"] == 0
        assert interaction["votes_down"] == 0
        assert interaction["views"] == 0
        assert interaction["shares"] == 0

    def test_all_keys_present(self):
        interaction = self._get_default_interaction()
        expected_keys = {"votes_up", "votes_down", "views", "shares"}
        assert set(interaction.keys()) == expected_keys


class TestInteractionUpdate:
    """Tests for updating interaction counts."""

    def test_increment_votes_up(self):
        interaction = {"votes_up": 5, "votes_down": 1}
        interaction["votes_up"] += 1
        assert interaction["votes_up"] == 6

    def test_increment_views(self):
        interaction = {"views": 100}
        interaction["views"] += 1
        assert interaction["views"] == 101

    def test_increment_shares(self):
        interaction = {"shares": 10}
        interaction["shares"] += 1
        assert interaction["shares"] == 11


# --- Extract headline ---


class TestExtractHeadline:
    """Tests for _extract_headline logic."""

    def _extract_headline(self, result):
        return (
            result.get("headline")
            or result.get("title")
            or result.get("query", "")[:100]
        )

    def test_from_headline(self):
        result = {"headline": "Breaking News"}
        assert self._extract_headline(result) == "Breaking News"

    def test_from_title_fallback(self):
        result = {"title": "News Title"}
        assert self._extract_headline(result) == "News Title"

    def test_from_query_fallback(self):
        result = {"query": "What is happening?"}
        assert self._extract_headline(result) == "What is happening?"

    def test_headline_takes_precedence(self):
        result = {
            "headline": "Primary",
            "title": "Secondary",
            "query": "Tertiary",
        }
        assert self._extract_headline(result) == "Primary"

    def test_empty_result(self):
        result = {}
        assert self._extract_headline(result) == ""

    def test_query_truncated_at_100(self):
        long_query = "A" * 200
        result = {"query": long_query}
        assert len(self._extract_headline(result)) == 100


# --- Extract summary ---


class TestExtractSummary:
    """Tests for _extract_summary logic."""

    def _extract_summary(self, result):
        return (
            result.get("summary")
            or result.get("current_knowledge")
            or result.get("formatted_findings", "")[:500]
        )

    def test_from_summary(self):
        result = {"summary": "Brief summary here"}
        assert self._extract_summary(result) == "Brief summary here"

    def test_from_current_knowledge(self):
        result = {"current_knowledge": "What we know so far"}
        assert self._extract_summary(result) == "What we know so far"

    def test_from_formatted_findings(self):
        result = {"formatted_findings": "Finding 1. Finding 2."}
        assert self._extract_summary(result) == "Finding 1. Finding 2."

    def test_summary_takes_precedence(self):
        result = {
            "summary": "Primary",
            "current_knowledge": "Secondary",
            "formatted_findings": "Tertiary",
        }
        assert self._extract_summary(result) == "Primary"

    def test_formatted_findings_truncated(self):
        long_findings = "B" * 600
        result = {"formatted_findings": long_findings}
        assert len(self._extract_summary(result)) == 500


# --- Calculate impact ---


class TestCalculateImpact:
    """Tests for _calculate_impact score logic."""

    def _calculate_impact(self, result):
        findings_count = len(result.get("findings", []))
        sources_count = len(result.get("sources", []))
        score = min(10, 5 + (findings_count // 5) + (sources_count // 3))
        return max(1, score)

    def test_no_findings_or_sources(self):
        result = {}
        assert self._calculate_impact(result) == 5

    def test_many_findings(self):
        result = {"findings": ["f"] * 15}
        # 5 + (15 // 5) = 5 + 3 = 8
        assert self._calculate_impact(result) == 8

    def test_many_sources(self):
        result = {"sources": ["s"] * 9}
        # 5 + 0 + (9 // 3) = 5 + 3 = 8
        assert self._calculate_impact(result) == 8

    def test_combined_high_score(self):
        result = {"findings": ["f"] * 20, "sources": ["s"] * 15}
        # 5 + (20 // 5) + (15 // 3) = 5 + 4 + 5 = 14, capped at 10
        assert self._calculate_impact(result) == 10

    def test_minimum_score_1(self):
        # Score formula always starts at 5, so min is 5
        result = {}
        assert self._calculate_impact(result) >= 1

    def test_maximum_score_10(self):
        result = {"findings": ["f"] * 100, "sources": ["s"] * 100}
        assert self._calculate_impact(result) == 10


# --- Extract topics ---


class TestExtractTopics:
    """Tests for _extract_topics logic."""

    def _extract_topics(self, result):
        topics = result.get("topics", [])
        if not topics and "query" in result:
            words = result["query"].lower().split()
            topics = [w for w in words if len(w) > 4][:5]
        return topics

    def test_from_topics_field(self):
        result = {"topics": ["AI", "Machine Learning", "Tech"]}
        assert self._extract_topics(result) == [
            "AI",
            "Machine Learning",
            "Tech",
        ]

    def test_from_query_when_no_topics(self):
        result = {"query": "artificial intelligence research developments"}
        topics = self._extract_topics(result)
        # Words > 4 chars: artificial, intelligence, research, developments
        assert "artificial" in topics
        assert "intelligence" in topics

    def test_max_5_topics_from_query(self):
        result = {
            "query": "a" * 5
            + " "
            + " ".join(["word" + str(i) for i in range(10)])
        }
        topics = self._extract_topics(result)
        assert len(topics) <= 5

    def test_filters_short_words(self):
        result = {"query": "the big and small new idea"}
        topics = self._extract_topics(result)
        # Only "small" is > 4 chars
        assert "the" not in topics
        assert "big" not in topics
        assert "and" not in topics
        assert "new" not in topics
        assert "idea" not in topics
        assert "small" in topics

    def test_empty_result(self):
        result = {}
        assert self._extract_topics(result) == []


# --- Extract entities ---


class TestExtractEntities:
    """Tests for _extract_entities logic."""

    def _extract_entities(self, result):
        return result.get(
            "entities", {"people": [], "places": [], "organizations": []}
        )

    def test_from_entities_field(self):
        result = {
            "entities": {
                "people": ["John Doe"],
                "places": ["New York"],
                "organizations": ["OpenAI"],
            }
        }
        entities = self._extract_entities(result)
        assert entities["people"] == ["John Doe"]
        assert entities["places"] == ["New York"]
        assert entities["organizations"] == ["OpenAI"]

    def test_default_empty_lists(self):
        result = {}
        entities = self._extract_entities(result)
        assert entities["people"] == []
        assert entities["places"] == []
        assert entities["organizations"] == []


# --- Get latest version ---


class TestGetLatestVersion:
    """Tests for get_latest_version logic."""

    def _get_latest_version(self, versions):
        if not versions:
            return None
        return max(versions, key=lambda v: v["created_at"])

    def test_no_versions(self):
        assert self._get_latest_version([]) is None

    def test_single_version(self):
        version = {"version_id": "v1", "created_at": datetime.now(timezone.utc)}
        result = self._get_latest_version([version])
        assert result["version_id"] == "v1"

    def test_multiple_versions(self):
        old = {
            "version_id": "v1",
            "created_at": datetime(2025, 1, 1, tzinfo=timezone.utc),
        }
        new = {
            "version_id": "v2",
            "created_at": datetime(2025, 6, 15, tzinfo=timezone.utc),
        }
        result = self._get_latest_version([old, new])
        assert result["version_id"] == "v2"

    def test_order_independent(self):
        new = {
            "version_id": "v2",
            "created_at": datetime(2025, 6, 15, tzinfo=timezone.utc),
        }
        old = {
            "version_id": "v1",
            "created_at": datetime(2025, 1, 1, tzinfo=timezone.utc),
        }
        result = self._get_latest_version([new, old])
        assert result["version_id"] == "v2"


# --- To base dict ---


class TestToBaseDict:
    """Tests for to_base_dict conversion."""

    def _to_base_dict(self, card):
        return {
            "id": card.get("id"),
            "topic": card.get("topic"),
            "user_id": card.get("user_id"),
            "created_at": card.get("created_at").isoformat()
            if card.get("created_at")
            else None,
            "updated_at": card.get("updated_at").isoformat()
            if card.get("updated_at")
            else None,
            "source": card.get("source"),
            "versions_count": len(card.get("versions", [])),
            "parent_card_id": card.get("parent_card_id"),
            "metadata": card.get("metadata", {}),
            "interaction": card.get("interaction", {}),
            "card_type": card.get("card_type"),
        }

    def test_includes_id(self):
        card = {"id": "card-123"}
        result = self._to_base_dict(card)
        assert result["id"] == "card-123"

    def test_includes_topic(self):
        card = {"topic": "AI News"}
        result = self._to_base_dict(card)
        assert result["topic"] == "AI News"

    def test_formats_created_at(self):
        dt = datetime(2025, 6, 15, 10, 0, 0, tzinfo=timezone.utc)
        card = {"created_at": dt}
        result = self._to_base_dict(card)
        assert "2025-06-15" in result["created_at"]

    def test_counts_versions(self):
        card = {"versions": [{"id": "v1"}, {"id": "v2"}]}
        result = self._to_base_dict(card)
        assert result["versions_count"] == 2

    def test_includes_interaction(self):
        card = {"interaction": {"votes_up": 10}}
        result = self._to_base_dict(card)
        assert result["interaction"]["votes_up"] == 10


# --- Card types ---


class TestCardTypes:
    """Tests for card type identifiers."""

    def test_news_card_type(self):
        assert "news" == "news"

    def test_research_card_type(self):
        assert "research" == "research"

    def test_update_card_type(self):
        assert "update" == "update"

    def test_overview_card_type(self):
        assert "overview" == "overview"


# --- NewsCard specific fields ---


class TestNewsCardFields:
    """Tests for NewsCard specific field defaults."""

    def _get_news_card_defaults(self):
        return {
            "headline": "",
            "summary": "",
            "category": "General",
            "impact_score": 5,
            "entities": {"people": [], "places": [], "organizations": []},
            "topics_extracted": [],
            "is_developing": False,
            "time_ago": "recent",
            "source_url": "",
            "analysis": "",
            "surprising_element": None,
        }

    def test_default_category(self):
        defaults = self._get_news_card_defaults()
        assert defaults["category"] == "General"

    def test_default_impact_score(self):
        defaults = self._get_news_card_defaults()
        assert defaults["impact_score"] == 5

    def test_default_is_developing(self):
        defaults = self._get_news_card_defaults()
        assert defaults["is_developing"] is False

    def test_default_time_ago(self):
        defaults = self._get_news_card_defaults()
        assert defaults["time_ago"] == "recent"

    def test_default_entities_structure(self):
        defaults = self._get_news_card_defaults()
        assert "people" in defaults["entities"]
        assert "places" in defaults["entities"]
        assert "organizations" in defaults["entities"]


# --- ResearchCard specific fields ---


class TestResearchCardFields:
    """Tests for ResearchCard specific field defaults."""

    def _get_research_card_defaults(self):
        return {
            "research_depth": "quick",
            "key_findings": [],
            "sources_count": 0,
        }

    def test_default_depth(self):
        defaults = self._get_research_card_defaults()
        assert defaults["research_depth"] == "quick"

    def test_valid_depths(self):
        valid_depths = ["quick", "detailed", "report"]
        for depth in valid_depths:
            assert depth in valid_depths

    def test_default_key_findings_empty(self):
        defaults = self._get_research_card_defaults()
        assert defaults["key_findings"] == []


# --- UpdateCard specific fields ---


class TestUpdateCardFields:
    """Tests for UpdateCard specific field defaults."""

    def _get_update_card_defaults(self):
        return {
            "update_type": "new_stories",
            "count": 0,
            "preview_items": [],
        }

    def test_default_update_type(self):
        defaults = self._get_update_card_defaults()
        assert defaults["update_type"] == "new_stories"

    def test_valid_update_types(self):
        valid_types = ["new_stories", "breaking", "follow_up"]
        for t in valid_types:
            assert t in valid_types


# --- OverviewCard specific fields ---


class TestOverviewCardFields:
    """Tests for OverviewCard specific field defaults."""

    def _get_overview_card_defaults(self):
        return {
            "topic": "News Overview",
            "stats": {
                "total_new": 0,
                "breaking": 0,
                "relevant": 0,
                "categories": {},
            },
            "summary": "",
            "top_stories": [],
            "trend_analysis": "",
        }

    def test_default_topic(self):
        defaults = self._get_overview_card_defaults()
        assert defaults["topic"] == "News Overview"

    def test_default_stats_structure(self):
        defaults = self._get_overview_card_defaults()
        assert "total_new" in defaults["stats"]
        assert "breaking" in defaults["stats"]
        assert "relevant" in defaults["stats"]
        assert "categories" in defaults["stats"]


# --- Save card data ---


class TestSaveCardData:
    """Tests for save() card_data structure."""

    def _build_save_data(self, card):
        return {
            "id": card.get("id"),
            "user_id": card.get("user_id"),
            "topic": card.get("topic"),
            "card_type": card.get("card_type"),
            "source_type": card.get("source", {}).get("type"),
            "source_id": card.get("source", {}).get("source_id"),
            "created_from": card.get("source", {}).get("created_from"),
            "parent_card_id": card.get("parent_card_id"),
            "metadata": card.get("metadata"),
        }

    def test_includes_source_fields(self):
        card = {
            "id": "c1",
            "source": {
                "type": "user_search",
                "source_id": "s1",
                "created_from": "Search",
            },
        }
        data = self._build_save_data(card)
        assert data["source_type"] == "user_search"
        assert data["source_id"] == "s1"
        assert data["created_from"] == "Search"


# --- Add version data ---


class TestAddVersionData:
    """Tests for add_version version_data structure."""

    def _build_version_data(self, research_results, query, strategy):
        return {
            "search_query": query,
            "research_result": research_results,
            "headline": research_results.get("headline", ""),
            "summary": research_results.get("summary", ""),
            "findings": research_results.get("findings", []),
            "sources": research_results.get("sources", []),
            "impact_score": 5,  # Simplified
            "topics": research_results.get("topics", []),
            "entities": research_results.get("entities", {}),
            "strategy": strategy,
        }

    def test_includes_search_query(self):
        data = self._build_version_data(
            {"headline": "Test"}, "test query", "quick"
        )
        assert data["search_query"] == "test query"

    def test_includes_strategy(self):
        data = self._build_version_data({}, "q", "focused_iteration")
        assert data["strategy"] == "focused_iteration"

    def test_includes_full_research_result(self):
        result = {"headline": "News", "summary": "Details"}
        data = self._build_version_data(result, "q", "s")
        assert data["research_result"] == result
