"""
Extended tests for news/core/card_factory.py

Tests cover:
- CardFactory.register_card_type() - type registration and validation
- CardFactory.get_storage() - session handling and Flask context
- CardFactory.create_card() - card creation with various types
- CardFactory.load_card() - card loading and reconstruction
- CardFactory.get_user_cards() - user-specific queries
- CardFactory.get_recent_cards() - time-based queries
- CardFactory.update_card() - card updates
- CardFactory.delete_card() - card deletion
- CardFactory._reconstruct_card() - edge cases and data handling
- CardFactory.create_news_card_from_analysis() - news item processing
- Module-level convenience functions
"""

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


class TestCardFactoryRegisterCardType:
    """Tests for CardFactory.register_card_type() method."""

    def test_register_valid_card_type(self):
        """Registering a valid BaseCard subclass succeeds."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import BaseCard

        class CustomCard(BaseCard):
            pass

        original_types = CardFactory._card_types.copy()
        try:
            CardFactory.register_card_type("custom", CustomCard)
            assert "custom" in CardFactory._card_types
            assert CardFactory._card_types["custom"] is CustomCard
        finally:
            CardFactory._card_types = original_types

    def test_register_non_basecard_raises_valueerror(self):
        """Registering a non-BaseCard class raises ValueError."""
        from local_deep_research.news.core.card_factory import CardFactory

        class NotACard:
            pass

        with pytest.raises(ValueError) as exc_info:
            CardFactory.register_card_type("invalid", NotACard)

        assert "must be a subclass of BaseCard" in str(exc_info.value)

    def test_register_overwrites_existing_type(self):
        """Registering an existing type name overwrites it."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import BaseCard

        class NewNewsCard(BaseCard):
            pass

        original_types = CardFactory._card_types.copy()
        try:
            CardFactory.register_card_type("news", NewNewsCard)
            assert CardFactory._card_types["news"] is NewNewsCard
        finally:
            CardFactory._card_types = original_types

    def test_register_empty_type_name(self):
        """Registering with empty string type name works."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import BaseCard

        class EmptyNameCard(BaseCard):
            pass

        original_types = CardFactory._card_types.copy()
        try:
            CardFactory.register_card_type("", EmptyNameCard)
            assert "" in CardFactory._card_types
        finally:
            CardFactory._card_types = original_types


class TestCardFactoryGetStorage:
    """Tests for CardFactory.get_storage() method."""

    def test_get_storage_with_provided_session(self):
        """Returns SQLCardStorage with provided session."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_session = MagicMock()
        storage = CardFactory.get_storage(mock_session)

        assert storage._session is mock_session

    def test_get_storage_without_session_raises_error(self):
        """Raises RuntimeError when no session provided and no Flask context."""
        from local_deep_research.news.core.card_factory import CardFactory

        # Without Flask, calling get_storage() with no session should fail
        with pytest.raises((RuntimeError, ImportError)):
            CardFactory.get_storage(None)

    def test_get_storage_always_works_with_session(self):
        """get_storage always works when session is provided."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.card_storage import SQLCardStorage

        mock_session = MagicMock()
        storage = CardFactory.get_storage(mock_session)

        assert isinstance(storage, SQLCardStorage)
        assert storage._session is mock_session

    def test_get_storage_session_required_for_non_flask(self):
        """Session is required when not in Flask context."""
        from local_deep_research.news.core.card_factory import CardFactory

        # This should raise because there's no Flask context
        try:
            CardFactory.get_storage()
            # If it didn't raise, it found a session somehow
        except (RuntimeError, ImportError):
            pass  # Expected


class TestCardFactoryCreateCard:
    """Tests for CardFactory.create_card() method."""

    def test_create_card_unknown_type_raises_valueerror(self):
        """Creating card with unknown type raises ValueError."""
        from local_deep_research.news.core.card_factory import CardFactory

        MagicMock()
        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            with pytest.raises(ValueError) as exc_info:
                CardFactory.create_card(
                    card_type="unknown_type",
                    topic="Test",
                    source=MagicMock(),
                    user_id="user123",
                )

            assert "Unknown card type" in str(exc_info.value)
            assert "unknown_type" in str(exc_info.value)

    def test_create_card_generates_uuid(self):
        """Created card gets a generated UUID."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import CardSource

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(
                type="test", source_id="src1", created_from="test"
            )
            card = CardFactory.create_card(
                card_type="news",
                topic="Test Topic",
                source=source,
                user_id="user123",
            )

            assert len(card.id) == 36  # UUID format
            mock_storage.create.assert_called_once()

    def test_create_card_with_additional_kwargs(self):
        """Additional kwargs are passed to card constructor."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import CardSource

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(
                type="test", source_id="src1", created_from="test"
            )
            card = CardFactory.create_card(
                card_type="news",
                topic="Test Topic",
                source=source,
                user_id="user123",
                category="Technology",
                impact_score=8,
            )

            assert card.category == "Technology"
            assert card.impact_score == 8

    def test_create_card_saves_to_storage(self):
        """Created card is saved to storage."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import CardSource

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(
                type="test", source_id="src1", created_from="test"
            )
            CardFactory.create_card(
                card_type="news",
                topic="Test Topic",
                source=source,
                user_id="user123",
            )

            mock_storage.create.assert_called_once()
            call_args = mock_storage.create.call_args[0][0]
            assert call_args["topic"] == "Test Topic"

    def test_create_news_card_type(self):
        """Creating news type card succeeds."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import CardSource, NewsCard

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(type="test", source_id="s1", created_from="t")
            card = CardFactory.create_card(
                card_type="news",
                topic="News Topic",
                source=source,
                user_id="user1",
            )

            assert isinstance(card, NewsCard)

    def test_create_research_card_type(self):
        """Creating research type card succeeds."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import (
            CardSource,
            ResearchCard,
        )

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(type="test", source_id="s1", created_from="t")
            card = CardFactory.create_card(
                card_type="research",
                topic="Research Topic",
                source=source,
                user_id="user1",
            )

            assert isinstance(card, ResearchCard)

    def test_create_update_card_type(self):
        """Creating update type card succeeds."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import (
            CardSource,
            UpdateCard,
        )

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(type="test", source_id="s1", created_from="t")
            card = CardFactory.create_card(
                card_type="update",
                topic="Update Topic",
                source=source,
                user_id="user1",
            )

            assert isinstance(card, UpdateCard)

    def test_create_overview_card_type(self):
        """Overview card type is registered."""
        from local_deep_research.news.core.card_factory import CardFactory

        # Verify overview is a registered card type
        assert "overview" in CardFactory._card_types


class TestCardFactoryLoadCard:
    """Tests for CardFactory.load_card() method."""

    def test_load_card_not_found_returns_none(self):
        """Loading non-existent card returns None."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.get.return_value = None

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            result = CardFactory.load_card("nonexistent-id")
            assert result is None

    def test_load_card_success(self):
        """Successfully loading a card returns reconstructed card."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.get.return_value = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test Topic",
            "user_id": "user123",
            "source": {"type": "test", "source_id": "s1", "created_from": "t"},
        }

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            card = CardFactory.load_card("card-123")
            assert card is not None
            assert card.id == "card-123"

    def test_load_card_with_versions(self):
        """Loading card with versions restores them."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.get.return_value = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
            "versions": [{"v": 1}, {"v": 2}],
        }

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            card = CardFactory.load_card("card-123")
            assert len(card.versions) == 2


class TestCardFactoryGetUserCards:
    """Tests for CardFactory.get_user_cards() method."""

    def test_get_user_cards_empty(self):
        """Returns empty list when no cards found."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.list.return_value = []

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            cards = CardFactory.get_user_cards("user123")
            assert cards == []

    def test_get_user_cards_applies_filters(self):
        """Filters are applied correctly."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.list.return_value = []

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            CardFactory.get_user_cards(
                "user123", card_types=["news", "research"], limit=10, offset=5
            )

            mock_storage.list.assert_called_once()
            call_kwargs = mock_storage.list.call_args[1]
            assert call_kwargs["filters"]["user_id"] == "user123"
            assert call_kwargs["filters"]["card_type"] == ["news", "research"]
            assert call_kwargs["limit"] == 10
            assert call_kwargs["offset"] == 5

    def test_get_user_cards_reconstructs_cards(self):
        """Cards are reconstructed from data."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.list.return_value = [
            {
                "id": "card-1",
                "card_type": "news",
                "topic": "Topic 1",
                "user_id": "user123",
                "source": {},
            },
            {
                "id": "card-2",
                "card_type": "news",
                "topic": "Topic 2",
                "user_id": "user123",
                "source": {},
            },
        ]

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            cards = CardFactory.get_user_cards("user123")
            assert len(cards) == 2
            assert cards[0].id == "card-1"
            assert cards[1].id == "card-2"

    def test_get_user_cards_skips_failed_reconstruction(self):
        """Cards that fail reconstruction are skipped."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.list.return_value = [
            {
                "id": "card-1",
                "card_type": "news",
                "topic": "Topic 1",
                "user_id": "user123",
                "source": {},
            },
            {
                "id": "card-2",
                "card_type": "invalid_type",  # Will fail reconstruction
                "topic": "Topic 2",
                "user_id": "user123",
                "source": {},
            },
        ]

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            cards = CardFactory.get_user_cards("user123")
            assert len(cards) == 1  # Only first card reconstructed


class TestCardFactoryGetRecentCards:
    """Tests for CardFactory.get_recent_cards() method."""

    def test_get_recent_cards_default_params(self):
        """Default parameters are applied."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.get_recent.return_value = []

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            CardFactory.get_recent_cards()

            mock_storage.get_recent.assert_called_once_with(
                hours=24, card_types=None, limit=50
            )

    def test_get_recent_cards_custom_params(self):
        """Custom parameters are passed correctly."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.get_recent.return_value = []

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            CardFactory.get_recent_cards(
                hours=48, card_types=["news"], limit=100
            )

            mock_storage.get_recent.assert_called_once_with(
                hours=48, card_types=["news"], limit=100
            )

    def test_get_recent_cards_reconstructs_cards(self):
        """Cards are reconstructed from data."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.get_recent.return_value = [
            {
                "id": "card-1",
                "card_type": "news",
                "topic": "Recent Topic",
                "user_id": "user123",
                "source": {},
            }
        ]

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            cards = CardFactory.get_recent_cards()
            assert len(cards) == 1
            assert cards[0].topic == "Recent Topic"


class TestCardFactoryUpdateCard:
    """Tests for CardFactory.update_card() method."""

    def test_update_card_calls_storage_update(self):
        """Update calls storage with card data."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.update.return_value = True

        mock_card = MagicMock()
        mock_card.id = "card-123"
        mock_card.to_dict.return_value = {"id": "card-123", "topic": "Updated"}
        mock_card.interaction = {"views": 5}

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            result = CardFactory.update_card(mock_card)

            assert result is True
            mock_storage.update.assert_called_once()

    def test_update_card_includes_interaction_data(self):
        """Interaction data is included in update."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.update.return_value = True

        mock_card = MagicMock()
        mock_card.id = "card-123"
        mock_card.to_dict.return_value = {"id": "card-123"}
        mock_card.interaction = {"viewed": True, "votes_up": 3}

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            CardFactory.update_card(mock_card)

            call_args = mock_storage.update.call_args
            assert call_args[0][1]["interaction"] == {
                "viewed": True,
                "votes_up": 3,
            }

    def test_update_card_with_session(self):
        """Session is passed to get_storage."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_session = MagicMock()
        mock_storage = MagicMock()
        mock_storage.update.return_value = True

        mock_card = MagicMock()
        mock_card.id = "card-123"
        mock_card.to_dict.return_value = {}
        mock_card.interaction = {}

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ) as mock_get:
            CardFactory.update_card(mock_card, session=mock_session)
            mock_get.assert_called_once_with(mock_session)


class TestCardFactoryDeleteCard:
    """Tests for CardFactory.delete_card() method."""

    def test_delete_card_success(self):
        """Deleting card returns True on success."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.delete.return_value = True

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            result = CardFactory.delete_card("card-123")

            assert result is True
            mock_storage.delete.assert_called_once_with("card-123")

    def test_delete_card_not_found(self):
        """Deleting non-existent card returns False."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.delete.return_value = False

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            result = CardFactory.delete_card("nonexistent")
            assert result is False


class TestCardFactoryReconstructCard:
    """Tests for CardFactory._reconstruct_card() method."""

    def test_reconstruct_card_unknown_type_returns_none(self):
        """Unknown card type returns None."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "unknown_type",
            "topic": "Test",
            "user_id": "user1",
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is None

    def test_reconstruct_card_handles_enum_card_type(self):
        """Handles card_type as enum with .value attribute."""
        from local_deep_research.news.core.card_factory import CardFactory

        class MockEnum:
            value = "news"

        card_data = {
            "id": "card-123",
            "card_type": MockEnum(),
            "topic": "Test",
            "user_id": "user1",
            "source": {},
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.id == "card-123"

    def test_reconstruct_card_builds_source_from_flat_fields(self):
        """Builds CardSource from flat fields when source is empty."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
            "source_type": "rss",
            "source_id": "feed-1",
            "created_from": "Import",
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.source.type == "rss"
        assert result.source.source_id == "feed-1"

    def test_reconstruct_card_handles_iso_string_dates(self):
        """Parses ISO string dates correctly."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
            "created_at": "2024-01-15T10:30:00+00:00",
            "updated_at": "2024-01-16T11:00:00Z",
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.created_at.year == 2024
        assert result.created_at.month == 1
        assert result.created_at.day == 15

    def test_reconstruct_card_handles_datetime_objects(self):
        """Handles datetime objects directly."""
        from local_deep_research.news.core.card_factory import CardFactory

        created = datetime(2024, 1, 15, 10, 30, 0, tzinfo=timezone.utc)

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
            "created_at": created,
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.created_at == created

    def test_reconstruct_card_restores_metadata(self):
        """Restores metadata from card data."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
            "metadata": {"key1": "value1", "key2": 42},
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result.metadata == {"key1": "value1", "key2": 42}

    def test_reconstruct_card_restores_interaction(self):
        """Restores interaction data from card data."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
            "interaction": {"views": 10, "voted": "up"},
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result.interaction == {"views": 10, "voted": "up"}

    def test_reconstruct_card_handles_none_user_id(self):
        """Handles None user_id by defaulting to 'unknown'."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": None,
            "source": {},
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.user_id == "unknown"

    def test_reconstruct_card_uses_title_when_no_topic(self):
        """Uses title when topic is not present."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "title": "Title from DB",
            "user_id": "user1",
            "source": {},
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result.topic == "Title from DB"

    def test_reconstruct_card_exception_returns_none(self):
        """Returns None when exception occurs."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            # Missing required 'id' key - will cause exception
        }

        # This should not raise, just return None
        CardFactory._reconstruct_card(card_data)
        # It may or may not return None depending on exact error
        # The point is it shouldn't raise


class TestCardFactoryCreateNewsCardFromAnalysis:
    """Tests for CardFactory.create_news_card_from_analysis() method."""

    def test_method_exists_with_correct_signature(self):
        """Method exists with expected parameters."""
        from local_deep_research.news.core.card_factory import CardFactory
        import inspect

        method = CardFactory.create_news_card_from_analysis
        sig = inspect.signature(method)

        # Check required parameters
        params = list(sig.parameters.keys())
        assert "news_item" in params
        assert "source_search_id" in params
        assert "user_id" in params
        assert "additional_metadata" in params

    def test_creates_card_source_with_news_search_type(self):
        """CardSource is created with type='news_search'."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={"headline": "Test"},
                source_search_id="search-123",
                user_id="user-456",
            )

            call_kwargs = mock.call_args[1]
            source = call_kwargs["source"]
            assert source.type == "news_search"
            assert source.source_id == "search-123"

    def test_passes_headline_as_topic(self):
        """Headline from news_item is passed as topic."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={"headline": "Breaking News"},
                source_search_id="search-123",
                user_id="user-456",
            )

            call_kwargs = mock.call_args[1]
            assert call_kwargs["topic"] == "Breaking News"

    def test_default_headline_is_untitled(self):
        """Default topic is 'Untitled' when headline not provided."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={},
                source_search_id="search-123",
                user_id="user-456",
            )

            call_kwargs = mock.call_args[1]
            assert call_kwargs["topic"] == "Untitled"

    def test_merges_additional_metadata(self):
        """Additional metadata is merged with news_item metadata."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={
                    "headline": "Test",
                    "metadata": {"original": "value"},
                },
                source_search_id="search-123",
                user_id="user-456",
                additional_metadata={"extra": "data"},
            )

            call_kwargs = mock.call_args[1]
            assert call_kwargs["metadata"]["original"] == "value"
            assert call_kwargs["metadata"]["extra"] == "data"

    def test_passes_category_with_default(self):
        """Category defaults to 'Other' when not provided."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={},
                source_search_id="search-123",
                user_id="user-456",
            )

            call_kwargs = mock.call_args[1]
            assert call_kwargs["category"] == "Other"

    def test_passes_impact_score_with_default(self):
        """Impact score defaults to 5 when not provided."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={},
                source_search_id="search-123",
                user_id="user-456",
            )

            call_kwargs = mock.call_args[1]
            assert call_kwargs["impact_score"] == 5

    def test_returns_created_card(self):
        """Returns the card from create_card."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()
        mock_card.id = "card-123"

        with patch.object(CardFactory, "create_card", return_value=mock_card):
            result = CardFactory.create_news_card_from_analysis(
                news_item={"headline": "Test"},
                source_search_id="search-123",
                user_id="user-456",
            )

            assert result is mock_card
            assert result.id == "card-123"

    def test_card_source_has_analyzer_metadata(self):
        """CardSource metadata includes analyzer version."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_card = MagicMock()

        with patch.object(
            CardFactory, "create_card", return_value=mock_card
        ) as mock:
            CardFactory.create_news_card_from_analysis(
                news_item={},
                source_search_id="search-123",
                user_id="user-456",
            )

            call_kwargs = mock.call_args[1]
            source = call_kwargs["source"]
            assert source.metadata["analyzer_version"] == "1.0"
            assert source.metadata["extraction_method"] == "llm"


class TestModuleLevelConvenienceFunctions:
    """Tests for module-level convenience functions."""

    def test_create_card_function(self):
        """create_card function delegates to CardFactory."""
        from local_deep_research.news.core.card_factory import (
            create_card,
            CardFactory,
        )
        from local_deep_research.news.core.base_card import CardSource

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(type="test", source_id="s1", created_from="t")
            card = create_card(
                card_type="news",
                topic="Test",
                source=source,
                user_id="user1",
            )

            assert card is not None

    def test_load_card_function(self):
        """load_card function delegates to CardFactory."""
        from local_deep_research.news.core.card_factory import (
            load_card,
            CardFactory,
        )

        mock_storage = MagicMock()
        mock_storage.get.return_value = None

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            result = load_card("card-123")
            assert result is None


class TestCardFactoryEdgeCases:
    """Edge cases and boundary condition tests."""

    def test_create_card_with_empty_topic(self):
        """Card can be created with empty topic."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import CardSource

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(type="test", source_id="s1", created_from="t")
            card = CardFactory.create_card(
                card_type="news", topic="", source=source, user_id="user1"
            )
            assert card.topic == ""

    def test_create_card_with_unicode_topic(self):
        """Card can be created with unicode topic."""
        from local_deep_research.news.core.card_factory import CardFactory
        from local_deep_research.news.core.base_card import CardSource

        mock_storage = MagicMock()

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            source = CardSource(type="test", source_id="s1", created_from="t")
            card = CardFactory.create_card(
                card_type="news",
                topic="新闻 📰 News",
                source=source,
                user_id="user1",
            )
            assert card.topic == "新闻 📰 News"

    def test_registered_card_types_are_correct(self):
        """Default registered card types are correct."""
        from local_deep_research.news.core.card_factory import CardFactory

        assert "news" in CardFactory._card_types
        assert "research" in CardFactory._card_types
        assert "update" in CardFactory._card_types
        assert "overview" in CardFactory._card_types

    def test_get_user_cards_with_no_card_types_filter(self):
        """Get user cards without card_types filter."""
        from local_deep_research.news.core.card_factory import CardFactory

        mock_storage = MagicMock()
        mock_storage.list.return_value = []

        with patch.object(
            CardFactory, "get_storage", return_value=mock_storage
        ):
            CardFactory.get_user_cards("user123", card_types=None)

            call_kwargs = mock_storage.list.call_args[1]
            assert "card_type" not in call_kwargs["filters"]

    def test_reconstruct_card_empty_source(self):
        """Reconstructing card with empty source dict."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            "source": {},
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.source.type == "unknown"

    def test_reconstruct_card_missing_source_key(self):
        """Reconstructing card with missing source key."""
        from local_deep_research.news.core.card_factory import CardFactory

        card_data = {
            "id": "card-123",
            "card_type": "news",
            "topic": "Test",
            "user_id": "user1",
            # No source key
        }

        result = CardFactory._reconstruct_card(card_data)
        assert result is not None
        assert result.source.type == "unknown"
