"""
Deep behavioral tests for card_factory.py pure logic.
Tests card type registry, card reconstruction logic,
source extraction, and user card filtering.
"""

from datetime import datetime, timezone


# --- Card type registry ---


class TestCardTypeRegistry:
    """Tests for card type registration."""

    def _get_default_types(self):
        return {
            "news": "NewsCard",
            "research": "ResearchCard",
            "update": "UpdateCard",
            "overview": "OverviewCard",
        }

    def test_has_news_type(self):
        types = self._get_default_types()
        assert "news" in types

    def test_has_research_type(self):
        types = self._get_default_types()
        assert "research" in types

    def test_has_update_type(self):
        types = self._get_default_types()
        assert "update" in types

    def test_has_overview_type(self):
        types = self._get_default_types()
        assert "overview" in types

    def test_exactly_four_types(self):
        types = self._get_default_types()
        assert len(types) == 4


class TestCardTypeRegistration:
    """Tests for registering new card types."""

    def test_register_adds_type(self):
        registry = {"news": "NewsCard"}
        registry["custom"] = "CustomCard"
        assert "custom" in registry

    def test_overwrite_existing_type(self):
        registry = {"news": "NewsCard"}
        registry["news"] = "NewNewsCard"
        assert registry["news"] == "NewNewsCard"


class TestCardTypeValidation:
    """Tests for card type validation."""

    def _is_valid_type(self, card_type, registry):
        return card_type in registry

    def test_valid_type(self):
        registry = {"news": "NewsCard"}
        assert self._is_valid_type("news", registry) is True

    def test_invalid_type(self):
        registry = {"news": "NewsCard"}
        assert self._is_valid_type("unknown", registry) is False


# --- Card reconstruction source extraction ---


class TestReconstructSourceExtraction:
    """Tests for extracting source data during reconstruction."""

    def _extract_source(self, card_data):
        source_data = card_data.get("source", {})
        if not source_data:
            source_data = {
                "type": card_data.get("source_type", "unknown"),
                "source_id": card_data.get("source_id"),
                "created_from": card_data.get("created_from", ""),
                "metadata": {},
            }
        return source_data

    def test_from_nested_source(self):
        card_data = {
            "source": {
                "type": "news_topic",
                "source_id": "t123",
                "created_from": "Topic",
                "metadata": {"key": "value"},
            }
        }
        source = self._extract_source(card_data)
        assert source["type"] == "news_topic"
        assert source["metadata"]["key"] == "value"

    def test_from_flat_fields(self):
        card_data = {
            "source_type": "user_search",
            "source_id": "s456",
            "created_from": "Search",
        }
        source = self._extract_source(card_data)
        assert source["type"] == "user_search"
        assert source["source_id"] == "s456"

    def test_default_type_unknown(self):
        card_data = {}
        source = self._extract_source(card_data)
        assert source["type"] == "unknown"


# --- Card reconstruction user_id handling ---


class TestReconstructUserIdHandling:
    """Tests for user_id handling during reconstruction."""

    def _get_user_id(self, card_data):
        return card_data.get("user_id") or "unknown"

    def test_has_user_id(self):
        card_data = {"user_id": "user123"}
        assert self._get_user_id(card_data) == "user123"

    def test_missing_user_id(self):
        card_data = {}
        assert self._get_user_id(card_data) == "unknown"

    def test_none_user_id(self):
        card_data = {"user_id": None}
        assert self._get_user_id(card_data) == "unknown"


# --- Card reconstruction topic handling ---


class TestReconstructTopicHandling:
    """Tests for topic handling during reconstruction."""

    def _get_topic(self, card_data):
        return card_data.get("topic") or card_data.get("title", "")

    def test_from_topic(self):
        card_data = {"topic": "AI News"}
        assert self._get_topic(card_data) == "AI News"

    def test_from_title_fallback(self):
        card_data = {"title": "News Title"}
        assert self._get_topic(card_data) == "News Title"

    def test_topic_takes_precedence(self):
        card_data = {"topic": "Primary", "title": "Secondary"}
        assert self._get_topic(card_data) == "Primary"

    def test_empty_when_missing(self):
        card_data = {}
        assert self._get_topic(card_data) == ""


# --- Card reconstruction datetime handling ---


class TestReconstructDatetimeHandling:
    """Tests for datetime parsing during reconstruction."""

    def _parse_datetime(self, value):
        if isinstance(value, str):
            return datetime.fromisoformat(value.replace("Z", "+00:00"))
        return value

    def test_iso_string(self):
        dt_str = "2025-06-15T10:30:00+00:00"
        result = self._parse_datetime(dt_str)
        assert result.year == 2025
        assert result.month == 6
        assert result.day == 15

    def test_iso_with_z_suffix(self):
        dt_str = "2025-06-15T10:30:00Z"
        result = self._parse_datetime(dt_str)
        assert result.year == 2025

    def test_datetime_object(self):
        dt = datetime(2025, 6, 15, 10, 30, tzinfo=timezone.utc)
        result = self._parse_datetime(dt)
        assert result == dt


# --- Card reconstruction card_type enum handling ---


class TestReconstructCardTypeEnumHandling:
    """Tests for card_type enum extraction during reconstruction."""

    def _get_card_type(self, card_data):
        card_type = card_data.get("card_type", "news")
        if hasattr(card_type, "value"):
            card_type = card_type.value
        return card_type

    def test_string_type(self):
        card_data = {"card_type": "research"}
        assert self._get_card_type(card_data) == "research"

    def test_default_news(self):
        card_data = {}
        assert self._get_card_type(card_data) == "news"

    def test_enum_like_object(self):
        class FakeEnum:
            value = "update"

        card_data = {"card_type": FakeEnum()}
        assert self._get_card_type(card_data) == "update"


# --- Get user cards filter building ---


class TestGetUserCardsFilterBuilding:
    """Tests for filter building in get_user_cards."""

    def _build_filter(self, user_id, card_types=None):
        filters = {"user_id": user_id}
        if card_types:
            filters["card_type"] = card_types
        return filters

    def test_basic_filter(self):
        filters = self._build_filter("user123")
        assert filters["user_id"] == "user123"
        assert "card_type" not in filters

    def test_with_card_types(self):
        filters = self._build_filter("user123", ["news", "research"])
        assert filters["card_type"] == ["news", "research"]


# --- Create news card from analysis ---


class TestCreateNewsCardFromAnalysis:
    """Tests for create_news_card_from_analysis data mapping."""

    def _build_card_source(self, source_search_id):
        return {
            "type": "news_search",
            "source_id": source_search_id,
            "created_from": "News analysis",
            "metadata": {"analyzer_version": "1.0", "extraction_method": "llm"},
        }

    def test_source_type(self):
        source = self._build_card_source("search-123")
        assert source["type"] == "news_search"

    def test_source_metadata(self):
        source = self._build_card_source("s1")
        assert source["metadata"]["analyzer_version"] == "1.0"
        assert source["metadata"]["extraction_method"] == "llm"


class TestNewsItemToCardMapping:
    """Tests for mapping news_item fields to card creation."""

    def _map_news_item(self, news_item, additional_metadata=None):
        metadata = news_item.get("metadata", {})
        if additional_metadata:
            metadata.update(additional_metadata)

        return {
            "topic": news_item.get("headline", "Untitled"),
            "category": news_item.get("category", "Other"),
            "summary": news_item.get("summary", ""),
            "analysis": news_item.get("analysis", ""),
            "impact_score": news_item.get("impact_score", 5),
            "entities": news_item.get("entities", {}),
            "topics": news_item.get("topics", []),
            "source_url": news_item.get("source_url", ""),
            "is_developing": news_item.get("is_developing", False),
            "surprising_element": news_item.get("surprising_element"),
            "metadata": metadata,
        }

    def test_maps_headline_to_topic(self):
        news_item = {"headline": "Breaking News"}
        result = self._map_news_item(news_item)
        assert result["topic"] == "Breaking News"

    def test_default_topic_untitled(self):
        news_item = {}
        result = self._map_news_item(news_item)
        assert result["topic"] == "Untitled"

    def test_default_category_other(self):
        news_item = {}
        result = self._map_news_item(news_item)
        assert result["category"] == "Other"

    def test_merges_metadata(self):
        news_item = {"metadata": {"key1": "val1"}}
        result = self._map_news_item(news_item, {"key2": "val2"})
        assert result["metadata"]["key1"] == "val1"
        assert result["metadata"]["key2"] == "val2"


# --- Card list reconstruction ---


class TestCardListReconstruction:
    """Tests for reconstructing list of cards."""

    def _reconstruct_cards(self, cards_data, reconstruct_fn):
        cards = []
        for data in cards_data:
            card = reconstruct_fn(data)
            if card:
                cards.append(card)
        return cards

    def test_filters_none_results(self):
        cards_data = [{"id": "1"}, {"id": "2"}, {"id": "3"}]

        def reconstruct(data):
            if data["id"] == "2":
                return None
            return data

        result = self._reconstruct_cards(cards_data, reconstruct)
        assert len(result) == 2

    def test_preserves_order(self):
        cards_data = [{"id": "1"}, {"id": "2"}]
        result = self._reconstruct_cards(cards_data, lambda x: x)
        assert result[0]["id"] == "1"
        assert result[1]["id"] == "2"


# --- Update card interaction handling ---


class TestUpdateCardInteractionHandling:
    """Tests for interaction data in card updates."""

    def _build_update_data(self, card_dict, interaction):
        card_data = card_dict.copy()
        card_data["interaction"] = interaction
        return card_data

    def test_includes_interaction(self):
        card_dict = {"id": "c1", "topic": "News"}
        interaction = {"votes_up": 10, "views": 100}
        result = self._build_update_data(card_dict, interaction)
        assert result["interaction"]["votes_up"] == 10


# --- Card factory error conditions ---


class TestCardFactoryErrorConditions:
    """Tests for error condition handling."""

    def _validate_card_type(self, card_type, registry):
        if card_type not in registry:
            raise ValueError(
                f"Unknown card type: {card_type}. "
                f"Available types: {list(registry.keys())}"
            )

    def test_raises_on_unknown_type(self):
        registry = {"news": "NewsCard"}
        try:
            self._validate_card_type("invalid", registry)
            assert False, "Should have raised ValueError"
        except ValueError as e:
            assert "Unknown card type: invalid" in str(e)

    def test_includes_available_types(self):
        registry = {"news": "NewsCard", "research": "ResearchCard"}
        try:
            self._validate_card_type("invalid", registry)
        except ValueError as e:
            assert "news" in str(e)
            assert "research" in str(e)


# --- Card reconstruction attribute restoration ---


class TestCardReconstructionAttributeRestoration:
    """Tests for restoring card attributes from stored data."""

    def _restore_attributes(self, card, card_data):
        card["versions"] = card_data.get("versions", [])
        card["metadata"] = card_data.get("metadata", {})
        card["interaction"] = card_data.get("interaction", {})
        return card

    def test_restores_versions(self):
        card = {}
        card_data = {"versions": [{"id": "v1"}]}
        result = self._restore_attributes(card, card_data)
        assert len(result["versions"]) == 1

    def test_defaults_empty_versions(self):
        card = {}
        card_data = {}
        result = self._restore_attributes(card, card_data)
        assert result["versions"] == []

    def test_restores_metadata(self):
        card = {}
        card_data = {"metadata": {"custom": "data"}}
        result = self._restore_attributes(card, card_data)
        assert result["metadata"]["custom"] == "data"

    def test_defaults_empty_metadata(self):
        card = {}
        card_data = {}
        result = self._restore_attributes(card, card_data)
        assert result["metadata"] == {}

    def test_restores_interaction(self):
        card = {}
        card_data = {"interaction": {"votes_up": 5}}
        result = self._restore_attributes(card, card_data)
        assert result["interaction"]["votes_up"] == 5


# --- Create card ID generation ---


class TestCreateCardIdGeneration:
    """Tests for card ID generation during creation."""

    def _is_valid_uuid(self, value):
        import re

        pattern = (
            r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        )
        return bool(re.match(pattern, value.lower()))

    def test_uuid_format(self):
        import uuid

        card_id = str(uuid.uuid4())
        assert self._is_valid_uuid(card_id) is True

    def test_each_call_unique(self):
        import uuid

        id1 = str(uuid.uuid4())
        id2 = str(uuid.uuid4())
        assert id1 != id2
