"""
Coverage tests for local_deep_research/news/core/base_card.py

Tests focus on paths not yet covered by existing tests:
- CardSource dataclass instantiation with/without optional fields
- CardVersion.__post_init__ generates version_id when none given
- BaseCard.__post_init__ initialises interaction dict and timestamps
- BaseCard.set_progress_callback / _update_progress with and without callback
- BaseCard._extract_headline fallback chain (headline → title → query)
- BaseCard._extract_summary fallback chain
- BaseCard._calculate_impact score clamping to [1, 10]
- BaseCard._extract_topics – keyword extraction when topics list is absent
- BaseCard._extract_entities returns entities dict from result
- NewsCard.__post_init__ sets headline from topic when blank
- NewsCard.to_dict includes all fields
- ResearchCard.to_dict / UpdateCard.to_dict / OverviewCard.to_dict shapes
- BaseCard.get_latest_version returns None when no versions
"""

from datetime import datetime, timezone
from typing import Dict, Any


from local_deep_research.news.core.base_card import (
    BaseCard,
    CardSource,
    CardVersion,
    NewsCard,
    OverviewCard,
    ResearchCard,
    UpdateCard,
)


# ---------------------------------------------------------------------------
# Concrete BaseCard subclass for testing
# ---------------------------------------------------------------------------


class _ConcreteCard(BaseCard):
    def get_card_type(self) -> str:
        return "concrete"

    def to_dict(self) -> Dict[str, Any]:
        return self.to_base_dict()


def _make_source(**kwargs):
    defaults = {"type": "user_search"}
    defaults.update(kwargs)
    return CardSource(**defaults)


def _make_concrete_card(**kwargs):
    defaults = {
        "topic": "Test Topic",
        "source": _make_source(),
        "user_id": "user1",
    }
    defaults.update(kwargs)
    return _ConcreteCard(**defaults)


# ---------------------------------------------------------------------------
# CardSource
# ---------------------------------------------------------------------------


class TestCardSource:
    def test_minimal_instantiation(self):
        src = CardSource(type="news_item")
        assert src.type == "news_item"
        assert src.source_id is None
        assert src.created_from == ""
        assert src.metadata == {}

    def test_full_instantiation(self):
        src = CardSource(
            type="subscription",
            source_id="s1",
            created_from="scheduler",
            metadata={"key": "val"},
        )
        assert src.source_id == "s1"
        assert src.metadata == {"key": "val"}


# ---------------------------------------------------------------------------
# CardVersion
# ---------------------------------------------------------------------------


class TestCardVersion:
    def test_version_id_generated_when_empty(self):
        cv = CardVersion(
            version_id="",
            created_at=datetime.now(timezone.utc),
            content={},
            query_used="q",
        )
        assert cv.version_id != ""

    def test_version_id_preserved_when_given(self):
        cv = CardVersion(
            version_id="fixed-id",
            created_at=datetime.now(timezone.utc),
            content={},
            query_used="q",
        )
        assert cv.version_id == "fixed-id"


# ---------------------------------------------------------------------------
# BaseCard.__post_init__
# ---------------------------------------------------------------------------


class TestBaseCardInit:
    def test_id_generated(self):
        card = _make_concrete_card()
        assert isinstance(card.id, str)
        assert len(card.id) > 0

    def test_card_id_used_as_id(self):
        card = _make_concrete_card(card_id="my-special-id")
        assert card.id == "my-special-id"

    def test_interaction_has_correct_keys(self):
        card = _make_concrete_card()
        for key in ("votes_up", "votes_down", "views", "shares"):
            assert key in card.interaction
            assert card.interaction[key] == 0

    def test_timestamps_are_set(self):
        card = _make_concrete_card()
        assert isinstance(card.created_at, datetime)
        assert isinstance(card.updated_at, datetime)


# ---------------------------------------------------------------------------
# Progress callback
# ---------------------------------------------------------------------------


class TestProgressCallback:
    def test_set_and_call_progress_callback(self):
        card = _make_concrete_card()
        calls = []
        card.set_progress_callback(
            lambda msg, pct, meta: calls.append((msg, pct, meta))
        )
        card._update_progress("step 1", 50, {"extra": True})
        assert calls == [("step 1", 50, {"extra": True})]

    def test_no_callback_does_not_raise(self):
        card = _make_concrete_card()
        card._update_progress("msg", 10, {})  # should silently pass


# ---------------------------------------------------------------------------
# _extract_headline
# ---------------------------------------------------------------------------


class TestExtractHeadline:
    def test_headline_field_preferred(self):
        card = _make_concrete_card()
        result = card._extract_headline(
            {"headline": "Top Story", "title": "Alt"}
        )
        assert result == "Top Story"

    def test_title_field_used_as_fallback(self):
        card = _make_concrete_card()
        result = card._extract_headline({"title": "Article Title"})
        assert result == "Article Title"

    def test_query_used_as_last_resort(self):
        card = _make_concrete_card()
        result = card._extract_headline({"query": "long query text here"})
        assert "long query" in result

    def test_empty_result_returns_empty_string(self):
        card = _make_concrete_card()
        result = card._extract_headline({})
        assert result == ""


# ---------------------------------------------------------------------------
# _extract_summary
# ---------------------------------------------------------------------------


class TestExtractSummary:
    def test_summary_field_preferred(self):
        card = _make_concrete_card()
        result = card._extract_summary(
            {"summary": "A summary", "current_knowledge": "Other"}
        )
        assert result == "A summary"

    def test_current_knowledge_fallback(self):
        card = _make_concrete_card()
        result = card._extract_summary({"current_knowledge": "Current info"})
        assert result == "Current info"

    def test_formatted_findings_fallback(self):
        card = _make_concrete_card()
        result = card._extract_summary({"formatted_findings": "findings text"})
        assert "findings text" in result


# ---------------------------------------------------------------------------
# _calculate_impact
# ---------------------------------------------------------------------------


class TestCalculateImpact:
    def test_empty_result_returns_minimum(self):
        card = _make_concrete_card()
        score = card._calculate_impact({})
        assert 1 <= score <= 10

    def test_many_findings_increases_score(self):
        card = _make_concrete_card()
        result = {"findings": [{}] * 20, "sources": ["url"] * 10}
        score = card._calculate_impact(result)
        assert score == 10  # capped at 10

    def test_score_at_least_1(self):
        card = _make_concrete_card()
        assert card._calculate_impact({}) >= 1


# ---------------------------------------------------------------------------
# _extract_topics
# ---------------------------------------------------------------------------


class TestExtractTopics:
    def test_uses_topics_field_when_present(self):
        card = _make_concrete_card()
        result = card._extract_topics({"topics": ["AI", "ML"]})
        assert result == ["AI", "ML"]

    def test_extracts_keywords_from_query(self):
        card = _make_concrete_card()
        result = card._extract_topics(
            {"query": "latest research papers about quantum computing"}
        )
        # Words longer than 4 chars from the query
        assert len(result) > 0
        for word in result:
            assert len(word) > 4

    def test_empty_result_returns_empty_list(self):
        card = _make_concrete_card()
        result = card._extract_topics({})
        assert result == []


# ---------------------------------------------------------------------------
# _extract_entities
# ---------------------------------------------------------------------------


class TestExtractEntities:
    def test_uses_entities_field_when_present(self):
        card = _make_concrete_card()
        ents = {"people": ["Alice"], "places": [], "organizations": ["Acme"]}
        result = card._extract_entities({"entities": ents})
        assert result == ents

    def test_returns_default_structure_when_absent(self):
        card = _make_concrete_card()
        result = card._extract_entities({})
        assert "people" in result
        assert "places" in result
        assert "organizations" in result


# ---------------------------------------------------------------------------
# get_latest_version
# ---------------------------------------------------------------------------


class TestGetLatestVersion:
    def test_returns_none_when_no_versions(self):
        card = _make_concrete_card()
        assert card.get_latest_version() is None


# ---------------------------------------------------------------------------
# to_base_dict
# ---------------------------------------------------------------------------


class TestToBaseDict:
    def test_contains_required_keys(self):
        card = _make_concrete_card()
        d = card.to_base_dict()
        for key in (
            "id",
            "topic",
            "user_id",
            "created_at",
            "card_type",
            "interaction",
        ):
            assert key in d

    def test_card_type_is_concrete(self):
        card = _make_concrete_card()
        assert card.to_base_dict()["card_type"] == "concrete"


# ---------------------------------------------------------------------------
# NewsCard
# ---------------------------------------------------------------------------


class TestNewsCard:
    def test_headline_defaults_to_topic(self):
        card = NewsCard(
            topic="Breaking News", source=_make_source(), user_id="u1"
        )
        assert card.headline == "Breaking News"

    def test_explicit_headline_preserved(self):
        card = NewsCard(
            topic="Topic",
            source=_make_source(),
            user_id="u1",
            headline="Custom Headline",
        )
        assert card.headline == "Custom Headline"

    def test_to_dict_shape(self):
        card = NewsCard(topic="Topic", source=_make_source(), user_id="u1")
        d = card.to_dict()
        for key in ("headline", "summary", "category", "impact_score"):
            assert key in d

    def test_get_card_type(self):
        card = NewsCard(topic="T", source=_make_source(), user_id="u1")
        assert card.get_card_type() == "news"


# ---------------------------------------------------------------------------
# ResearchCard
# ---------------------------------------------------------------------------


class TestResearchCard:
    def test_to_dict_shape(self):
        card = ResearchCard(topic="T", source=_make_source(), user_id="u1")
        d = card.to_dict()
        assert "research_depth" in d
        assert "key_findings" in d

    def test_get_card_type(self):
        assert (
            ResearchCard(
                topic="T", source=_make_source(), user_id="u1"
            ).get_card_type()
            == "research"
        )


# ---------------------------------------------------------------------------
# UpdateCard
# ---------------------------------------------------------------------------


class TestUpdateCard:
    def test_to_dict_has_since(self):
        card = UpdateCard(topic="T", source=_make_source(), user_id="u1")
        d = card.to_dict()
        assert "since" in d
        assert "update_type" in d

    def test_get_card_type(self):
        assert (
            UpdateCard(
                topic="T", source=_make_source(), user_id="u1"
            ).get_card_type()
            == "update"
        )


# ---------------------------------------------------------------------------
# OverviewCard
# ---------------------------------------------------------------------------


class TestOverviewCard:
    def test_topic_is_news_overview(self):
        card = OverviewCard(source=_make_source(), user_id="u1")
        assert card.topic == "News Overview"

    def test_to_dict_has_stats(self):
        card = OverviewCard(source=_make_source(), user_id="u1")
        d = card.to_dict()
        assert "stats" in d
        assert "top_stories" in d

    def test_get_card_type(self):
        assert (
            OverviewCard(source=_make_source(), user_id="u1").get_card_type()
            == "overview"
        )
