from unittest.mock import patch

import pytest

from deerflow.agents.memory.thread_summary import (
    ThreadMemoryConflictError,
    _extract_json_object,
    apply_thread_memory_summary,
    render_thread_memory_summary,
)


def test_render_thread_memory_summary_returns_text():
    fake_storage = type(
        "S",
        (),
        {"load": lambda self, tid: {"threadId": tid, "user": {}, "history": {}, "facts": [], "memoryVersion": 2}},
    )()
    fake_model = type("M", (), {"invoke": lambda self, prompt: type("R", (), {"content": "用户总结"})()})()

    with (
        patch("deerflow.agents.memory.thread_summary.get_thread_memory_storage", return_value=fake_storage),
        patch("deerflow.agents.memory.thread_summary._get_summary_model", return_value=fake_model),
    ):
        result = render_thread_memory_summary("t1")

    assert result["threadId"] == "t1"
    assert result["memoryVersion"] == 2
    assert result["summary"] == "用户总结"


def test_apply_thread_memory_summary_raises_conflict_on_cas_failure():
    class _Storage:
        def load(self, _tid):
            return {"threadId": "t1", "ownerId": None, "user": {}, "history": {}, "facts": [], "memoryVersion": 1}

        def save(self, _tid, _data, expected_version=None):
            return False

    fake_model = type("M", (), {"invoke": lambda self, prompt: type("R", (), {"content": "{}"})()})()
    fake_updater = type("U", (), {"_scrub_sensitive": lambda self, data, _thread_id: data})()

    with (
        patch("deerflow.agents.memory.thread_summary.get_thread_memory_storage", return_value=_Storage()),
        patch("deerflow.agents.memory.thread_summary._get_summary_model", return_value=fake_model),
        patch("deerflow.agents.memory.thread_summary.ThreadMemoryUpdater", return_value=fake_updater),
    ):
        with pytest.raises(ThreadMemoryConflictError):
            apply_thread_memory_summary("t1", "更新内容", 1)


def test_apply_thread_memory_summary_falls_back_when_model_output_is_not_json():
    class _Storage:
        def __init__(self):
            self.saved = None

        def load(self, _tid):
            if self.saved is not None:
                return {"threadId": "t1", "memoryVersion": 2, **self.saved}
            return {
                "threadId": "t1",
                "ownerId": None,
                "user": {"topOfMind": {"summary": ""}},
                "history": {},
                "facts": [],
                "memoryVersion": 1,
            }

        def save(self, _tid, data, expected_version=None):
            self.saved = data
            return True

    storage = _Storage()
    fake_model = type("M", (), {"invoke": lambda self, prompt: type("R", (), {"content": "这是自然语言，不是JSON"})()})()
    fake_updater = type("U", (), {"_scrub_sensitive": lambda self, data, _thread_id: data})()

    with (
        patch("deerflow.agents.memory.thread_summary.get_thread_memory_storage", return_value=storage),
        patch("deerflow.agents.memory.thread_summary._get_summary_model", return_value=fake_model),
        patch("deerflow.agents.memory.thread_summary.ThreadMemoryUpdater", return_value=fake_updater),
    ):
        result = apply_thread_memory_summary("t1", "我最近在做线程记忆功能", 1)

    assert storage.saved is not None
    assert storage.saved["user"]["topOfMind"]["summary"] == "我最近在做线程记忆功能"
    assert result["user"]["topOfMind"]["summary"] == "我最近在做线程记忆功能"


def test_extract_json_object_repairs_inner_unescaped_quotes():
    raw = """
{
  "user": {
    "topOfMind": {
      "summary": "反感“作为 AI"这种句式，认为回答不用寒暄直接说重点。"
    }
  },
  "history": {},
  "facts": []
}
""".strip()
    parsed = _extract_json_object(raw)
    assert parsed is not None
    assert parsed["user"]["topOfMind"]["summary"].startswith("反感“作为 AI")
