from unittest.mock import MagicMock, patch

from deerflow.agents.memory.prompt import format_conversation_for_update
from deerflow.agents.memory.updater import (
    MemoryUpdater,
    _extract_text,
    clear_memory_data,
    create_memory_fact,
    delete_memory_fact,
    import_memory_data,
    update_memory_fact,
)
from deerflow.config.memory_config import MemoryConfig


def _make_memory(facts: list[dict[str, object]] | None = None) -> dict[str, object]:
    return {
        "version": "1.0",
        "lastUpdated": "",
        "user": {
            "workContext": {"summary": "", "updatedAt": ""},
            "personalContext": {"summary": "", "updatedAt": ""},
            "topOfMind": {"summary": "", "updatedAt": ""},
        },
        "history": {
            "recentMonths": {"summary": "", "updatedAt": ""},
            "earlierContext": {"summary": "", "updatedAt": ""},
            "longTermBackground": {"summary": "", "updatedAt": ""},
        },
        "facts": facts or [],
    }


def _memory_config(**overrides: object) -> MemoryConfig:
    config = MemoryConfig()
    for key, value in overrides.items():
        setattr(config, key, value)
    return config


def test_apply_updates_skips_existing_duplicate_and_preserves_removals() -> None:
    updater = MemoryUpdater()
    current_memory = _make_memory(
        facts=[
            {
                "id": "fact_existing",
                "content": "User likes Python",
                "category": "preference",
                "confidence": 0.9,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-a",
            },
            {
                "id": "fact_remove",
                "content": "Old context to remove",
                "category": "context",
                "confidence": 0.8,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-a",
            },
        ]
    )
    update_data = {
        "factsToRemove": ["fact_remove"],
        "newFacts": [
            {"content": "User likes Python", "category": "preference", "confidence": 0.95},
        ],
    }

    with patch(
        "deerflow.agents.memory.updater.get_memory_config",
        return_value=_memory_config(max_facts=100, fact_confidence_threshold=0.7),
    ):
        result = updater._apply_updates(current_memory, update_data, thread_id="thread-b")

    assert [fact["content"] for fact in result["facts"]] == ["User likes Python"]
    assert all(fact["id"] != "fact_remove" for fact in result["facts"])


def test_apply_updates_skips_same_batch_duplicates_and_keeps_source_metadata() -> None:
    updater = MemoryUpdater()
    current_memory = _make_memory()
    update_data = {
        "newFacts": [
            {"content": "User prefers dark mode", "category": "preference", "confidence": 0.91},
            {"content": "User prefers dark mode", "category": "preference", "confidence": 0.92},
            {"content": "User works on DeerFlow", "category": "context", "confidence": 0.87},
        ],
    }

    with patch(
        "deerflow.agents.memory.updater.get_memory_config",
        return_value=_memory_config(max_facts=100, fact_confidence_threshold=0.7),
    ):
        result = updater._apply_updates(current_memory, update_data, thread_id="thread-42")

    assert [fact["content"] for fact in result["facts"]] == [
        "User prefers dark mode",
        "User works on DeerFlow",
    ]
    assert all(fact["id"].startswith("fact_") for fact in result["facts"])
    assert all(fact["source"] == "thread-42" for fact in result["facts"])


def test_apply_updates_preserves_threshold_and_max_facts_trimming() -> None:
    updater = MemoryUpdater()
    current_memory = _make_memory(
        facts=[
            {
                "id": "fact_python",
                "content": "User likes Python",
                "category": "preference",
                "confidence": 0.95,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-a",
            },
            {
                "id": "fact_dark_mode",
                "content": "User prefers dark mode",
                "category": "preference",
                "confidence": 0.8,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-a",
            },
        ]
    )
    update_data = {
        "newFacts": [
            {"content": "User prefers dark mode", "category": "preference", "confidence": 0.9},
            {"content": "User uses uv", "category": "context", "confidence": 0.85},
            {"content": "User likes noisy logs", "category": "behavior", "confidence": 0.6},
        ],
    }

    with patch(
        "deerflow.agents.memory.updater.get_memory_config",
        return_value=_memory_config(max_facts=2, fact_confidence_threshold=0.7),
    ):
        result = updater._apply_updates(current_memory, update_data, thread_id="thread-9")

    assert [fact["content"] for fact in result["facts"]] == [
        "User likes Python",
        "User uses uv",
    ]
    assert all(fact["content"] != "User likes noisy logs" for fact in result["facts"])
    assert result["facts"][1]["source"] == "thread-9"


def test_apply_updates_preserves_source_error() -> None:
    updater = MemoryUpdater()
    current_memory = _make_memory()
    update_data = {
        "newFacts": [
            {
                "content": "Use make dev for local development.",
                "category": "correction",
                "confidence": 0.95,
                "sourceError": "The agent previously suggested npm start.",
            }
        ]
    }

    with patch(
        "deerflow.agents.memory.updater.get_memory_config",
        return_value=_memory_config(max_facts=100, fact_confidence_threshold=0.7),
    ):
        result = updater._apply_updates(current_memory, update_data, thread_id="thread-correction")

    assert result["facts"][0]["sourceError"] == "The agent previously suggested npm start."
    assert result["facts"][0]["category"] == "correction"


def test_apply_updates_ignores_empty_source_error() -> None:
    updater = MemoryUpdater()
    current_memory = _make_memory()
    update_data = {
        "newFacts": [
            {
                "content": "Use make dev for local development.",
                "category": "correction",
                "confidence": 0.95,
                "sourceError": "   ",
            }
        ]
    }

    with patch(
        "deerflow.agents.memory.updater.get_memory_config",
        return_value=_memory_config(max_facts=100, fact_confidence_threshold=0.7),
    ):
        result = updater._apply_updates(current_memory, update_data, thread_id="thread-correction")

    assert "sourceError" not in result["facts"][0]


def test_clear_memory_data_resets_all_sections() -> None:
    with patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True):
        result = clear_memory_data()

    assert result["version"] == "1.0"
    assert result["facts"] == []
    assert result["user"]["workContext"]["summary"] == ""
    assert result["history"]["recentMonths"]["summary"] == ""


def test_delete_memory_fact_removes_only_matching_fact() -> None:
    current_memory = _make_memory(
        facts=[
            {
                "id": "fact_keep",
                "content": "User likes Python",
                "category": "preference",
                "confidence": 0.9,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-a",
            },
            {
                "id": "fact_delete",
                "content": "User prefers tabs",
                "category": "preference",
                "confidence": 0.8,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-b",
            },
        ]
    )

    with (
        patch("deerflow.agents.memory.updater.get_memory_data", return_value=current_memory),
        patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
    ):
        result = delete_memory_fact("fact_delete")

    assert [fact["id"] for fact in result["facts"]] == ["fact_keep"]


def test_create_memory_fact_appends_manual_fact() -> None:
    with (
        patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
        patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
    ):
        result = create_memory_fact(
            content="  User prefers concise code reviews.  ",
            category="preference",
            confidence=0.88,
        )

    assert len(result["facts"]) == 1
    assert result["facts"][0]["content"] == "User prefers concise code reviews."
    assert result["facts"][0]["category"] == "preference"
    assert result["facts"][0]["confidence"] == 0.88
    assert result["facts"][0]["source"] == "manual"


def test_create_memory_fact_rejects_empty_content() -> None:
    try:
        create_memory_fact(content="   ")
    except ValueError as exc:
        assert exc.args == ("content",)
    else:
        raise AssertionError("Expected ValueError for empty fact content")


def test_create_memory_fact_rejects_invalid_confidence() -> None:
    for confidence in (-0.1, 1.1, float("nan"), float("inf"), float("-inf")):
        try:
            create_memory_fact(content="User likes tests", confidence=confidence)
        except ValueError as exc:
            assert exc.args == ("confidence",)
        else:
            raise AssertionError("Expected ValueError for invalid fact confidence")


def test_delete_memory_fact_raises_for_unknown_id() -> None:
    with patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()):
        try:
            delete_memory_fact("fact_missing")
        except KeyError as exc:
            assert exc.args == ("fact_missing",)
        else:
            raise AssertionError("Expected KeyError for missing fact id")


def test_import_memory_data_saves_and_returns_imported_memory() -> None:
    imported_memory = _make_memory(
        facts=[
            {
                "id": "fact_import",
                "content": "User works on DeerFlow.",
                "category": "context",
                "confidence": 0.87,
                "createdAt": "2026-03-20T00:00:00Z",
                "source": "manual",
            }
        ]
    )
    mock_storage = MagicMock()
    mock_storage.save.return_value = True
    mock_storage.load.return_value = imported_memory

    with patch("deerflow.agents.memory.updater.get_memory_storage", return_value=mock_storage):
        result = import_memory_data(imported_memory)

    mock_storage.save.assert_called_once_with(imported_memory, None)
    mock_storage.load.assert_called_once_with(None)
    assert result == imported_memory


def test_update_memory_fact_updates_only_matching_fact() -> None:
    current_memory = _make_memory(
        facts=[
            {
                "id": "fact_keep",
                "content": "User likes Python",
                "category": "preference",
                "confidence": 0.9,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "thread-a",
            },
            {
                "id": "fact_edit",
                "content": "User prefers tabs",
                "category": "preference",
                "confidence": 0.8,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "manual",
            },
        ]
    )

    with (
        patch("deerflow.agents.memory.updater.get_memory_data", return_value=current_memory),
        patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
    ):
        result = update_memory_fact(
            fact_id="fact_edit",
            content="User prefers spaces",
            category="workflow",
            confidence=0.91,
        )

    assert result["facts"][0]["content"] == "User likes Python"
    assert result["facts"][1]["content"] == "User prefers spaces"
    assert result["facts"][1]["category"] == "workflow"
    assert result["facts"][1]["confidence"] == 0.91
    assert result["facts"][1]["createdAt"] == "2026-03-18T00:00:00Z"
    assert result["facts"][1]["source"] == "manual"


def test_update_memory_fact_preserves_omitted_fields() -> None:
    current_memory = _make_memory(
        facts=[
            {
                "id": "fact_edit",
                "content": "User prefers tabs",
                "category": "preference",
                "confidence": 0.8,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "manual",
            },
        ]
    )

    with (
        patch("deerflow.agents.memory.updater.get_memory_data", return_value=current_memory),
        patch("deerflow.agents.memory.updater._save_memory_to_file", return_value=True),
    ):
        result = update_memory_fact(
            fact_id="fact_edit",
            content="User prefers spaces",
        )

    assert result["facts"][0]["content"] == "User prefers spaces"
    assert result["facts"][0]["category"] == "preference"
    assert result["facts"][0]["confidence"] == 0.8


def test_update_memory_fact_raises_for_unknown_id() -> None:
    with patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()):
        try:
            update_memory_fact(
                fact_id="fact_missing",
                content="User prefers concise code reviews.",
                category="preference",
                confidence=0.88,
            )
        except KeyError as exc:
            assert exc.args == ("fact_missing",)
        else:
            raise AssertionError("Expected KeyError for missing fact id")


def test_update_memory_fact_rejects_invalid_confidence() -> None:
    current_memory = _make_memory(
        facts=[
            {
                "id": "fact_edit",
                "content": "User prefers tabs",
                "category": "preference",
                "confidence": 0.8,
                "createdAt": "2026-03-18T00:00:00Z",
                "source": "manual",
            },
        ]
    )

    for confidence in (-0.1, 1.1, float("nan"), float("inf"), float("-inf")):
        with patch(
            "deerflow.agents.memory.updater.get_memory_data",
            return_value=current_memory,
        ):
            try:
                update_memory_fact(
                    fact_id="fact_edit",
                    content="User prefers spaces",
                    confidence=confidence,
                )
            except ValueError as exc:
                assert exc.args == ("confidence",)
            else:
                raise AssertionError("Expected ValueError for invalid fact confidence")


# ---------------------------------------------------------------------------
# _extract_text - LLM response content normalization
# ---------------------------------------------------------------------------


class TestExtractText:
    """_extract_text should normalize all content shapes to plain text."""

    def test_string_passthrough(self):
        assert _extract_text("hello world") == "hello world"

    def test_list_single_text_block(self):
        assert _extract_text([{"type": "text", "text": "hello"}]) == "hello"

    def test_list_multiple_text_blocks_joined(self):
        content = [
            {"type": "text", "text": "part one"},
            {"type": "text", "text": "part two"},
        ]
        assert _extract_text(content) == "part one\npart two"

    def test_list_plain_strings(self):
        assert _extract_text(["raw string"]) == "raw string"

    def test_list_string_chunks_join_without_separator(self):
        content = ['{"user"', ': "alice"}']
        assert _extract_text(content) == '{"user": "alice"}'

    def test_list_mixed_strings_and_blocks(self):
        content = [
            "raw text",
            {"type": "text", "text": "block text"},
        ]
        assert _extract_text(content) == "raw text\nblock text"

    def test_list_adjacent_string_chunks_then_block(self):
        content = [
            "prefix",
            "-continued",
            {"type": "text", "text": "block text"},
        ]
        assert _extract_text(content) == "prefix-continued\nblock text"

    def test_list_skips_non_text_blocks(self):
        content = [
            {"type": "image_url", "image_url": {"url": "http://img.png"}},
            {"type": "text", "text": "actual text"},
        ]
        assert _extract_text(content) == "actual text"

    def test_empty_list(self):
        assert _extract_text([]) == ""

    def test_list_no_text_blocks(self):
        assert _extract_text([{"type": "image_url", "image_url": {}}]) == ""

    def test_non_str_non_list(self):
        assert _extract_text(42) == "42"


# ---------------------------------------------------------------------------
# format_conversation_for_update - handles mixed list content
# ---------------------------------------------------------------------------


class TestFormatConversationForUpdate:
    def test_plain_string_messages(self):
        human_msg = MagicMock()
        human_msg.type = "human"
        human_msg.content = "What is Python?"

        ai_msg = MagicMock()
        ai_msg.type = "ai"
        ai_msg.content = "Python is a programming language."

        result = format_conversation_for_update([human_msg, ai_msg])
        assert "User: What is Python?" in result
        assert "Assistant: Python is a programming language." in result

    def test_list_content_with_plain_strings(self):
        """Plain strings in list content should not be lost."""
        msg = MagicMock()
        msg.type = "human"
        msg.content = ["raw user text", {"type": "text", "text": "structured text"}]

        result = format_conversation_for_update([msg])
        assert "raw user text" in result
        assert "structured text" in result


# ---------------------------------------------------------------------------
# update_memory - structured LLM response handling
# ---------------------------------------------------------------------------


class TestUpdateMemoryStructuredResponse:
    """update_memory should handle LLM responses returned as list content blocks."""

    def _make_mock_model(self, content):
        model = MagicMock()
        response = MagicMock()
        response.content = content
        model.invoke.return_value = response
        return model

    def test_string_response_parses(self):
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'

        with (
            patch.object(updater, "_get_model", return_value=self._make_mock_model(valid_json)),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "Hello"
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Hi there"
            ai_msg.tool_calls = []
            result = updater.update_memory([msg, ai_msg])

        assert result is True

    def test_list_content_response_parses(self):
        """LLM response as list-of-blocks should be extracted, not repr'd."""
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'
        list_content = [{"type": "text", "text": valid_json}]

        with (
            patch.object(updater, "_get_model", return_value=self._make_mock_model(list_content)),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "Hello"
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Hi"
            ai_msg.tool_calls = []
            result = updater.update_memory([msg, ai_msg])

        assert result is True

    def test_correction_hint_injected_when_detected(self):
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'
        model = self._make_mock_model(valid_json)

        with (
            patch.object(updater, "_get_model", return_value=model),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "No, that's wrong."
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Understood"
            ai_msg.tool_calls = []

            result = updater.update_memory([msg, ai_msg], correction_detected=True)

        assert result is True
        prompt = model.invoke.call_args[0][0]
        assert "Explicit correction signals were detected" in prompt

    def test_correction_hint_empty_when_not_detected(self):
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'
        model = self._make_mock_model(valid_json)

        with (
            patch.object(updater, "_get_model", return_value=model),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "Let's talk about memory."
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Sure"
            ai_msg.tool_calls = []

            result = updater.update_memory([msg, ai_msg], correction_detected=False)

        assert result is True
        prompt = model.invoke.call_args[0][0]
        assert "Explicit correction signals were detected" not in prompt


class TestFactDeduplicationCaseInsensitive:
    """Tests that fact deduplication is case-insensitive."""

    def test_duplicate_fact_different_case_not_stored(self):
        updater = MemoryUpdater()
        current_memory = _make_memory(
            facts=[
                {
                    "id": "fact_1",
                    "content": "User prefers Python",
                    "category": "preference",
                    "confidence": 0.9,
                    "createdAt": "2026-01-01T00:00:00Z",
                    "source": "thread-a",
                },
            ]
        )
        # Same fact with different casing should be treated as duplicate
        update_data = {
            "factsToRemove": [],
            "newFacts": [
                {"content": "user prefers python", "category": "preference", "confidence": 0.95},
            ],
        }

        with patch(
            "deerflow.agents.memory.updater.get_memory_config",
            return_value=_memory_config(max_facts=100, fact_confidence_threshold=0.7),
        ):
            result = updater._apply_updates(current_memory, update_data, thread_id="thread-b")

        # Should still have only 1 fact (duplicate rejected)
        assert len(result["facts"]) == 1
        assert result["facts"][0]["content"] == "User prefers Python"

    def test_unique_fact_different_case_and_content_stored(self):
        updater = MemoryUpdater()
        current_memory = _make_memory(
            facts=[
                {
                    "id": "fact_1",
                    "content": "User prefers Python",
                    "category": "preference",
                    "confidence": 0.9,
                    "createdAt": "2026-01-01T00:00:00Z",
                    "source": "thread-a",
                },
            ]
        )
        update_data = {
            "factsToRemove": [],
            "newFacts": [
                {"content": "User prefers Go", "category": "preference", "confidence": 0.85},
            ],
        }

        with patch(
            "deerflow.agents.memory.updater.get_memory_config",
            return_value=_memory_config(max_facts=100, fact_confidence_threshold=0.7),
        ):
            result = updater._apply_updates(current_memory, update_data, thread_id="thread-b")

        assert len(result["facts"]) == 2


class TestReinforcementHint:
    """Tests that reinforcement_detected injects the correct hint into the prompt."""

    @staticmethod
    def _make_mock_model(json_response: str):
        model = MagicMock()
        response = MagicMock()
        response.content = f"```json\n{json_response}\n```"
        model.invoke.return_value = response
        return model

    def test_reinforcement_hint_injected_when_detected(self):
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'
        model = self._make_mock_model(valid_json)

        with (
            patch.object(updater, "_get_model", return_value=model),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "Yes, exactly! That's what I needed."
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Great to hear!"
            ai_msg.tool_calls = []

            result = updater.update_memory([msg, ai_msg], reinforcement_detected=True)

        assert result is True
        prompt = model.invoke.call_args[0][0]
        assert "Positive reinforcement signals were detected" in prompt

    def test_reinforcement_hint_absent_when_not_detected(self):
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'
        model = self._make_mock_model(valid_json)

        with (
            patch.object(updater, "_get_model", return_value=model),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "Tell me more."
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Sure."
            ai_msg.tool_calls = []

            result = updater.update_memory([msg, ai_msg], reinforcement_detected=False)

        assert result is True
        prompt = model.invoke.call_args[0][0]
        assert "Positive reinforcement signals were detected" not in prompt

    def test_both_hints_present_when_both_detected(self):
        updater = MemoryUpdater()
        valid_json = '{"user": {}, "history": {}, "newFacts": [], "factsToRemove": []}'
        model = self._make_mock_model(valid_json)

        with (
            patch.object(updater, "_get_model", return_value=model),
            patch("deerflow.agents.memory.updater.get_memory_config", return_value=_memory_config(enabled=True)),
            patch("deerflow.agents.memory.updater.get_memory_data", return_value=_make_memory()),
            patch("deerflow.agents.memory.updater.get_memory_storage", return_value=MagicMock(save=MagicMock(return_value=True))),
        ):
            msg = MagicMock()
            msg.type = "human"
            msg.content = "No wait, that's wrong. Actually yes, exactly right."
            ai_msg = MagicMock()
            ai_msg.type = "ai"
            ai_msg.content = "Got it."
            ai_msg.tool_calls = []

            result = updater.update_memory([msg, ai_msg], correction_detected=True, reinforcement_detected=True)

        assert result is True
        prompt = model.invoke.call_args[0][0]
        assert "Explicit correction signals were detected" in prompt
        assert "Positive reinforcement signals were detected" in prompt
