"""
Embedding 持久化单元测试
=======================
测试 TreeIndex 的 embedding 序列化/反序列化功能。
"""

import json
import os
import tempfile
from pathlib import Path

import numpy as np
import pytest

from video_tree_trm.tree_index import (
    L1Node,
    L2Node,
    L3Node,
    IndexMeta,
    TreeIndex,
    _embed_to_str,
    _embed_from_str,
)


class TestEmbeddingSerialization:
    """测试 embedding 序列化辅助函数。"""

    def test_embed_to_str_and_back(self):
        """测试 base64 序列化/反序列化往返正确。"""
        arr = np.random.randn(768).astype(np.float32)
        s = _embed_to_str(arr)
        recovered = _embed_from_str(s)

        assert recovered is not None
        assert recovered.dtype == np.float32
        assert recovered.shape == (768,)
        np.testing.assert_array_almost_equal(arr, recovered, decimal=6)

    def test_embed_to_str_handles_none(self):
        """测试 None 输入返回 None。"""
        assert _embed_to_str(None) is None
        assert _embed_from_str(None) is None
        assert _embed_from_str("") is None


class TestL3NodeEmbedding:
    """测试 L3Node embedding 序列化。"""

    def test_l3_to_dict_without_embedding(self):
        """测试不带 embedding 序列化。"""
        node = L3Node(
            id="l3_0",
            description="测试描述",
            timestamp=1.0,
            frame_path="frame.jpg",
            raw_content="原始内容",
        )
        d = {
            "id": node.id,
            "description": node.description,
            "timestamp": node.timestamp,
            "frame_path": node.frame_path,
            "raw_content": node.raw_content,
        }
        # 无 embedding 字段
        assert "embedding" not in d

    def test_l3_embedding_roundtrip(self):
        """测试 L3 embedding 序列化往返。"""
        embed = np.random.randn(768).astype(np.float32)
        s = _embed_to_str(embed)
        recovered = _embed_from_str(s)
        np.testing.assert_array_almost_equal(embed, recovered, decimal=6)


class TestTreeIndexEmbedding:
    """测试 TreeIndex 完整序列化/反序列化。"""

    def test_save_load_without_embedding(self):
        """测试不带 embedding 保存/加载（向后兼容）。"""
        with tempfile.TemporaryDirectory() as tmpdir:
            path = os.path.join(tmpdir, "test.json")

            # 创建简单树
            l3 = L3Node(id="l3_0", description="L3 描述")
            l2 = L2Node(id="l2_0", description="L2 描述", children=[l3])
            l1 = L1Node(id="l1_0", summary="L1 摘要", children=[l2])
            meta = IndexMeta(source_path="test.mp4", modality="video")
            tree = TreeIndex(metadata=meta, roots=[l1])

            # 保存（不含 embedding）
            tree.save_json(path, include_embedding=False)

            # 加载
            loaded = TreeIndex.load_json(path)

            assert len(loaded.roots) == 1
            assert loaded.roots[0].summary == "L1 摘要"
            assert not loaded.is_embedded  # 无 embedding

    def test_save_load_with_embedding(self):
        """测试带 embedding 保存/加载。"""
        with tempfile.TemporaryDirectory() as tmpdir:
            path = os.path.join(tmpdir, "test_embed.json")

            # 创建带 embedding 的树
            embed_l1 = np.random.randn(768).astype(np.float32)
            embed_l2 = np.random.randn(768).astype(np.float32)
            embed_l3 = np.random.randn(768).astype(np.float32)

            l3 = L3Node(id="l3_0", description="L3 描述", embedding=embed_l3)
            l2 = L2Node(id="l2_0", description="L2 描述", embedding=embed_l2, children=[l3])
            l1 = L1Node(id="l1_0", summary="L1 摘要", embedding=embed_l1, children=[l2])
            meta = IndexMeta(
                source_path="test.mp4",
                modality="video",
                embed_model="test-model",
                embed_dim=768,
            )
            tree = TreeIndex(metadata=meta, roots=[l1])

            # 保存（含 embedding）
            tree.save_json(path, include_embedding=True)

            # 验证 JSON 中有 embedding 字段
            with open(path, encoding="utf-8") as f:
                data = json.load(f)
            assert "embedding" in data["roots"][0]
            assert data["metadata"]["embed_model"] == "test-model"

            # 加载
            loaded = TreeIndex.load_json(path)

            assert loaded.is_embedded
            np.testing.assert_array_almost_equal(
                loaded.roots[0].embedding, embed_l1, decimal=6
            )
            np.testing.assert_array_almost_equal(
                loaded.roots[0].children[0].embedding, embed_l2, decimal=6
            )
            np.testing.assert_array_almost_equal(
                loaded.roots[0].children[0].children[0].embedding, embed_l3, decimal=6
            )

    def test_load_old_format_compatible(self):
        """测试加载旧格式（无 embedding 字段）JSON 兼容。"""
        with tempfile.TemporaryDirectory() as tmpdir:
            path = os.path.join(tmpdir, "old_format.json")

            # 手动创建旧格式 JSON（无 embedding 字段）
            old_data = {
                "metadata": {
                    "source_path": "test.mp4",
                    "modality": "video",
                    "created_at": "2024-01-01T00:00:00",
                },
                "roots": [
                    {
                        "id": "l1_0",
                        "summary": "L1 摘要",
                        "time_range": None,
                        "children": [
                            {
                                "id": "l2_0",
                                "description": "L2 描述",
                                "time_range": None,
                                "children": [
                                    {
                                        "id": "l3_0",
                                        "description": "L3 描述",
                                        "timestamp": 1.0,
                                        "frame_path": None,
                                        "raw_content": None,
                                    }
                                ],
                            }
                        ],
                    }
                ],
            }
            with open(path, "w", encoding="utf-8") as f:
                json.dump(old_data, f)

            # 加载
            loaded = TreeIndex.load_json(path)

            assert len(loaded.roots) == 1
            assert not loaded.is_embedded  # 无 embedding，向后兼容


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
