import json

from deerflow.agents.memory.thread_storage import SqliteThreadMemoryStorage


def _payload():
    return {
        "ownerId": None,
        "user": {
            "workContext": {"summary": "Frontend engineer", "updatedAt": "2026-05-08T00:00:00Z"},
            "personalContext": {"summary": "Prefers Chinese", "updatedAt": "2026-05-08T00:00:00Z"},
            "topOfMind": {"summary": "Thread memory migration", "updatedAt": "2026-05-08T00:00:00Z"},
        },
        "history": {
            "recentMonths": {"summary": "Worked on memory features", "updatedAt": "2026-05-08T00:00:00Z"},
            "earlierContext": {"summary": "", "updatedAt": ""},
            "longTermBackground": {"summary": "Builds web products", "updatedAt": "2026-05-08T00:00:00Z"},
        },
        "facts": [],
    }


def test_sqlite_thread_memory_compare_and_swap(tmp_path):
    storage = SqliteThreadMemoryStorage(str(tmp_path / "thread-memory.db"))
    thread_id = "thread-1"

    assert storage.save(thread_id, _payload(), expected_version=0) is True
    loaded = storage.load(thread_id)
    assert loaded is not None
    assert loaded["memoryVersion"] == 0

    # wrong expected version should fail
    assert storage.save(thread_id, _payload(), expected_version=9) is False
    # correct version should pass and increment
    assert storage.save(thread_id, _payload(), expected_version=0) is True
    loaded2 = storage.load(thread_id)
    assert loaded2 is not None
    assert loaded2["memoryVersion"] == 1


def test_sqlite_thread_memory_saves_json_payload(tmp_path):
    db_path = tmp_path / "thread-memory.db"
    storage = SqliteThreadMemoryStorage(str(db_path))
    thread_id = "thread-md"

    assert storage.save(thread_id, _payload(), expected_version=0) is True

    with storage._lock:
        row = storage._conn.execute("SELECT memory_json FROM thread_memory WHERE thread_id = ?", (thread_id,)).fetchone()
    assert row is not None
    assert isinstance(row[0], str)
    parsed = json.loads(row[0])
    assert parsed["user"]["workContext"]["summary"] == "Frontend engineer"


def test_sqlite_thread_memory_uses_owner_id_column_when_json_missing_owner(tmp_path):
    db_path = tmp_path / "thread-memory.db"
    storage = SqliteThreadMemoryStorage(str(db_path))
    thread_id = "thread-load"
    payload = _payload()

    with storage._lock:
        storage._conn.execute(
            """
            INSERT INTO thread_memory (thread_id, owner_id, memory_json, memory_version, last_updated)
            VALUES (?, ?, ?, 0, datetime('now'))
            """,
            (
                thread_id,
                "owner-1",
                json.dumps(
                    {
                        "user": payload["user"],
                        "history": payload["history"],
                        "facts": [],
                    },
                    ensure_ascii=False,
                ),
            ),
        )
        storage._conn.commit()

    loaded = storage.load(thread_id)
    assert loaded is not None
    assert loaded["ownerId"] == "owner-1"
    assert loaded["user"]["workContext"]["summary"] == "Frontend engineer"
    assert loaded["facts"] == []


def test_sqlite_thread_memory_backfill_is_noop_after_migration(tmp_path):
    db_path = tmp_path / "thread-memory.db"
    storage = SqliteThreadMemoryStorage(str(db_path))

    assert storage.count_legacy_rows() == 0
    stats = storage.backfill_legacy_rows()
    assert stats["scanned"] == 0
    assert stats["updated"] == 0
    assert stats["failed"] == 0
    assert storage.count_legacy_rows() == 0
