"""
Deep behavioral tests for preference_manager/storage.py pure logic.
Tests upsert patterns, liked/disliked list management, list capping,
and embedding handling patterns.
"""


# --- Create defaults ---


class TestPreferenceCreateDefaults:
    """Tests for preference creation default values."""

    def _apply_defaults(self, data):
        return {
            "user_id": data["user_id"],
            "liked_categories": data.get("liked_categories", []),
            "disliked_categories": data.get("disliked_categories", []),
            "liked_topics": data.get("liked_topics", []),
            "disliked_topics": data.get("disliked_topics", []),
            "impact_threshold": data.get("impact_threshold", 5),
            "focus_preferences": data.get("focus_preferences", {}),
            "custom_prompt": data.get("custom_prompt"),
            "custom_search_terms": data.get("custom_search_terms"),
            "preference_embedding": data.get("preference_embedding"),
            "liked_news_ids": data.get("liked_news_ids", []),
            "disliked_news_ids": data.get("disliked_news_ids", []),
        }

    def test_liked_categories_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["liked_categories"] == []

    def test_disliked_categories_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["disliked_categories"] == []

    def test_liked_topics_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["liked_topics"] == []

    def test_disliked_topics_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["disliked_topics"] == []

    def test_impact_threshold_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["impact_threshold"] == 5

    def test_focus_preferences_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["focus_preferences"] == {}

    def test_custom_prompt_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["custom_prompt"] is None

    def test_liked_news_ids_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["liked_news_ids"] == []

    def test_disliked_news_ids_default(self):
        result = self._apply_defaults({"user_id": "u1"})
        assert result["disliked_news_ids"] == []

    def test_custom_values_preserved(self):
        data = {
            "user_id": "u1",
            "liked_categories": ["Tech", "Science"],
            "impact_threshold": 8,
        }
        result = self._apply_defaults(data)
        assert result["liked_categories"] == ["Tech", "Science"]
        assert result["impact_threshold"] == 8


# --- Upsert logic ---


class TestUpsertLogic:
    """Tests for create-or-update preference logic."""

    def _should_update(self, existing):
        return existing is not None

    def test_update_when_exists(self):
        existing = {"id": 1, "user_id": "u1"}
        assert self._should_update(existing) is True

    def test_create_when_none(self):
        assert self._should_update(None) is False

    def test_create_when_empty_dict(self):
        # Empty dict is truthy
        assert self._should_update({}) is True


class TestUpsertUserIdHandling:
    """Tests for user_id handling in upsert."""

    def _prepare_create_data(self, user_id, preferences):
        preferences["user_id"] = user_id
        return preferences

    def test_adds_user_id(self):
        result = self._prepare_create_data("u1", {"liked_topics": ["AI"]})
        assert result["user_id"] == "u1"

    def test_overwrites_existing_user_id(self):
        result = self._prepare_create_data("u1", {"user_id": "old"})
        assert result["user_id"] == "u1"


# --- Liked/disliked list management ---


class TestLikedListManagement:
    """Tests for adding items to liked list."""

    def _add_to_list(self, current_list, item_id, max_items=100):
        if current_list is None:
            current_list = []
        if item_id not in current_list:
            current_list.append(item_id)
            current_list = current_list[-max_items:]
        return current_list

    def test_add_first_item(self):
        result = self._add_to_list([], "item1")
        assert result == ["item1"]

    def test_add_to_none(self):
        result = self._add_to_list(None, "item1")
        assert result == ["item1"]

    def test_add_second_item(self):
        result = self._add_to_list(["item1"], "item2")
        assert result == ["item1", "item2"]

    def test_no_duplicate(self):
        result = self._add_to_list(["item1"], "item1")
        assert result == ["item1"]

    def test_max_100_items(self):
        current = [f"item{i}" for i in range(100)]
        result = self._add_to_list(current, "item100")
        assert len(result) == 100
        assert result[0] == "item1"  # First item removed
        assert result[-1] == "item100"

    def test_keeps_last_100(self):
        current = [f"item{i}" for i in range(150)]
        result = self._add_to_list(current, "item150", max_items=100)
        assert len(result) == 100
        assert result[-1] == "item150"


class TestDislikedListManagement:
    """Tests for adding items to disliked list."""

    def _add_disliked(self, current, item_id, item_type="news", max_items=100):
        if item_type != "news":
            return current
        if current is None:
            current = []
        if item_id not in current:
            current.append(item_id)
            return current[-max_items:]
        return current

    def test_add_news_item(self):
        result = self._add_disliked([], "n1", "news")
        assert result == ["n1"]

    def test_ignore_non_news_type(self):
        result = self._add_disliked([], "c1", "card")
        assert result == []

    def test_no_duplicate_disliked(self):
        result = self._add_disliked(["n1"], "n1", "news")
        assert result == ["n1"]


# --- Item type checking ---


class TestItemTypeChecking:
    """Tests for item_type validation."""

    def _is_news_item(self, item_type):
        return item_type == "news"

    def test_news_type(self):
        assert self._is_news_item("news") is True

    def test_card_type(self):
        assert self._is_news_item("card") is False

    def test_other_type(self):
        assert self._is_news_item("other") is False

    def test_empty_type(self):
        assert self._is_news_item("") is False


# --- Embedding handling ---


class TestEmbeddingHandling:
    """Tests for preference embedding patterns."""

    def _update_embedding(self, existing_prefs, embedding):
        if existing_prefs is None:
            return {"preference_embedding": embedding}
        existing_prefs["preference_embedding"] = embedding
        return existing_prefs

    def test_set_embedding_on_none(self):
        result = self._update_embedding(None, [0.1, 0.2, 0.3])
        assert result["preference_embedding"] == [0.1, 0.2, 0.3]

    def test_update_existing_embedding(self):
        prefs = {"user_id": "u1", "preference_embedding": [0.0]}
        result = self._update_embedding(prefs, [1.0, 2.0])
        assert result["preference_embedding"] == [1.0, 2.0]

    def test_preserves_other_fields(self):
        prefs = {"user_id": "u1", "liked_topics": ["AI"]}
        result = self._update_embedding(prefs, [0.5])
        assert result["user_id"] == "u1"
        assert result["liked_topics"] == ["AI"]


# --- Update field validation ---


class TestUpdateFieldValidation:
    """Tests for update field validation patterns."""

    VALID_FIELDS = {
        "liked_categories",
        "disliked_categories",
        "liked_topics",
        "disliked_topics",
        "impact_threshold",
        "focus_preferences",
        "custom_prompt",
        "custom_search_terms",
        "preference_embedding",
    }

    def _is_valid_field(self, field):
        return field in self.VALID_FIELDS

    def test_liked_categories_valid(self):
        assert self._is_valid_field("liked_categories") is True

    def test_impact_threshold_valid(self):
        assert self._is_valid_field("impact_threshold") is True

    def test_preference_embedding_valid(self):
        assert self._is_valid_field("preference_embedding") is True

    def test_user_id_not_valid_for_update(self):
        assert self._is_valid_field("user_id") is False

    def test_id_not_valid_for_update(self):
        assert self._is_valid_field("id") is False


# --- Create preferences for liked/disliked item ---


class TestCreatePrefsForLikedItem:
    """Tests for creating prefs when adding liked item."""

    def _create_for_liked(self, user_id, item_id, item_type):
        return {
            "user_id": user_id,
            "liked_news_ids": [item_id] if item_type == "news" else [],
            "disliked_news_ids": [],
        }

    def test_creates_with_liked_id(self):
        result = self._create_for_liked("u1", "n1", "news")
        assert result["liked_news_ids"] == ["n1"]
        assert result["disliked_news_ids"] == []

    def test_non_news_type(self):
        result = self._create_for_liked("u1", "c1", "card")
        assert result["liked_news_ids"] == []


class TestCreatePrefsForDislikedItem:
    """Tests for creating prefs when adding disliked item."""

    def _create_for_disliked(self, user_id, item_id, item_type):
        return {
            "user_id": user_id,
            "liked_news_ids": [],
            "disliked_news_ids": [item_id] if item_type == "news" else [],
        }

    def test_creates_with_disliked_id(self):
        result = self._create_for_disliked("u1", "n1", "news")
        assert result["disliked_news_ids"] == ["n1"]
        assert result["liked_news_ids"] == []


# --- List filtering ---


class TestListFiltering:
    """Tests for preference list filtering patterns."""

    def _should_filter_user(self, filters):
        return filters and "user_id" in filters

    def test_filter_by_user(self):
        assert self._should_filter_user({"user_id": "u1"}) is True

    def test_no_filter(self):
        assert not self._should_filter_user(None)

    def test_empty_filter(self):
        assert not self._should_filter_user({})


# --- ID conversion ---


class TestIdConversion:
    """Tests for ID type conversion patterns."""

    def _to_int_id(self, id_str):
        return int(id_str)

    def test_string_to_int(self):
        assert self._to_int_id("123") == 123

    def test_numeric_string(self):
        assert self._to_int_id("0") == 0

    def test_large_id(self):
        assert self._to_int_id("999999") == 999999


# --- Preference dict structure ---


class TestPreferenceDictStructure:
    """Tests for preference dict expected fields."""

    EXPECTED_FIELDS = [
        "id",
        "user_id",
        "liked_categories",
        "disliked_categories",
        "liked_topics",
        "disliked_topics",
        "impact_threshold",
        "focus_preferences",
        "custom_prompt",
        "custom_search_terms",
        "preference_embedding",
        "liked_news_ids",
        "disliked_news_ids",
    ]

    def test_has_id(self):
        assert "id" in self.EXPECTED_FIELDS

    def test_has_user_id(self):
        assert "user_id" in self.EXPECTED_FIELDS

    def test_has_liked_categories(self):
        assert "liked_categories" in self.EXPECTED_FIELDS

    def test_has_disliked_categories(self):
        assert "disliked_categories" in self.EXPECTED_FIELDS

    def test_has_impact_threshold(self):
        assert "impact_threshold" in self.EXPECTED_FIELDS

    def test_has_preference_embedding(self):
        assert "preference_embedding" in self.EXPECTED_FIELDS

    def test_has_liked_news_ids(self):
        assert "liked_news_ids" in self.EXPECTED_FIELDS

    def test_has_disliked_news_ids(self):
        assert "disliked_news_ids" in self.EXPECTED_FIELDS
