"""
测试存储层 append-only 机制及相关改动。

覆盖范围：
1. add_message 不再受 token 限制拒绝（存储层始终接受）
2. current_messages append-only，不再被 _compact_context 截断
3. 时间戳统一为 ISO 8601，兼容旧版 float
4. messages ↔ structured_messages 同步一致性
5. 全链路：add_message → save → load → 数据完整
6. system prompt 同步更新
"""
import sys
import json
import tempfile
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))

import pytest
from aacode.utils.session_manager import SessionManager, SessionMessage


# =============================================================================
# 辅助函数
# =============================================================================

def _read_session_file(sm):
    """读取当前会话的 JSON 文件"""
    f = sm.sessions_dir / f"{sm.current_session_id}.json"
    with open(f) as fh:
        return json.load(fh)


# =============================================================================
# 基础 Fixture
# =============================================================================

@pytest.fixture
def sm():
    with tempfile.TemporaryDirectory() as td:
        yield SessionManager(Path(td))


# =============================================================================
# 1. Append-Only 存储
# =============================================================================

class TestAppendOnly:
    """存储层 append-only，永不压缩"""

    @pytest.mark.asyncio
    async def test_messages_grow_without_compaction(self, sm):
        """current_messages 只增不减，即使 max_tokens 极低（存储层始终接受）"""
        low_limit = SessionManager(sm.project_path, max_tokens=1)
        await low_limit.create_session("test")
        initial = len(low_limit.current_messages)
        for i in range(50):
            ok = await low_limit.add_message("user", f"msg {i}")
            assert ok, f"add_message should never reject at index {i}"
        assert len(low_limit.current_messages) == initial + 50

    @pytest.mark.asyncio
    async def test_large_message_accepted(self, sm):
        """超长消息也被接受，不受 token 限制"""
        await sm.create_session("test")
        ok = await sm.add_message("user", "X" * 50000)
        assert ok
        assert len(sm.current_messages) >= 2

    @pytest.mark.asyncio
    async def test_multiple_roles_preserved(self, sm):
        """不同 role 的消息全部保留"""
        await sm.create_session("task")
        msgs = [
            ("assistant", "resp1"),
            ("tool", "result1"),
            ("assistant", "resp2", {"id": "c1"}),
        ]
        for m in msgs:
            if len(m) == 3:
                await sm.add_message(m[0], m[1], tool_call_id=m[2]["id"])
            else:
                await sm.add_message(m[0], m[1])
        assert len(sm.current_messages) >= 4
        roles = [m.role for m in sm.current_messages if m.role != "system"]
        assert roles == ["user", "assistant", "tool", "assistant"]


# =============================================================================
# 2. 时间戳格式
# =============================================================================

class TestTimestampFormat:
    """时间戳统一为 ISO 8601，兼容旧版 float"""

    @pytest.mark.asyncio
    async def test_new_messages_use_iso_format(self, sm):
        """新消息使用 ISO 8601 字符串"""
        await sm.create_session("test")
        await sm.add_message("user", "hello")
        ts = sm.current_messages[-1].timestamp
        assert isinstance(ts, str), f"Expected string, got {type(ts)}"
        assert "T" in ts, f"Expected ISO 8601 format, got {ts}"

    @pytest.mark.asyncio
    async def test_iso_format_persisted(self, sm):
        """ISO 8601 时间戳保存到 JSON 后格式不变"""
        await sm.create_session("test")
        await sm.add_message("user", "hello")
        await sm._save_session()
        raw = _read_session_file(sm)
        last = raw["messages"][-1]
        assert isinstance(last["timestamp"], str)
        assert "T" in last["timestamp"]

    @pytest.mark.asyncio
    async def test_load_old_float_timestamp(self, sm):
        """旧版 float 时间戳加载时自动转为 ISO 8601（兼容性）"""
        await sm.create_session("test")
        await sm._save_session()
        raw = _read_session_file(sm)
        # 模拟旧版 float 格式
        raw["messages"][0]["timestamp"] = 1779937951.0
        raw["created_at"] = 1779937951.0
        with open(sm.sessions_dir / f"{sm.current_session_id}.json", "w") as f:
            json.dump(raw, f)

        # 重新加载 —— 旧 float 应被自动转为 ISO 字符串
        sm2 = SessionManager(sm.project_path)
        ok = await sm2.switch_session(sm.current_session_id)
        assert ok
        ts = sm2.current_messages[0].timestamp
        assert isinstance(ts, str), f"Expected string after migration, got {type(ts)}"
        assert "T" in ts, f"Expected ISO 8601 format after migration, got {ts}"
        # 验证时间戳对应的实际时间正确
        assert ts.startswith("2026"), f"Expected 2026 timestamp, got {ts}"

    @pytest.mark.asyncio
    async def test_created_at_iso_format(self, sm):
        """会话级 created_at 也是 ISO 8601"""
        await sm.create_session("test")
        sid = sm.current_session_id
        created = sm.sessions_index[sid].created_at
        assert isinstance(created, str)
        assert "T" in created


# =============================================================================
# 3. 结构化字段单轨存储
# =============================================================================

class TestStructuredFieldsSingleTrack:
    """所有结构化字段（tool_calls / reasoning_content / tool_call_id）存储在单轨 current_messages 中"""

    @pytest.mark.asyncio
    async def test_add_message_saves_tool_calls(self, sm):
        """add_message 保存 tool_calls 到 current_messages"""
        await sm.create_session("test")
        tc = [{"id": "c1", "name": "run_shell", "arguments": '{"cmd":"ls"}'}]
        await sm.add_message("assistant", "checking", tool_calls=tc,
                             reasoning_content="need to check")
        assert sm.current_messages[-1].tool_calls == tc
        assert sm.current_messages[-1].reasoning_content == "need to check"

    @pytest.mark.asyncio
    async def test_tool_call_id_in_current_messages(self, sm):
        """tool 消息的 tool_call_id 保存在 current_messages 中"""
        await sm.create_session("test")
        await sm.add_message("tool", "result", tool_call_id="c1")
        assert sm.current_messages[-1].tool_call_id == "c1"

    @pytest.mark.asyncio
    async def test_structured_fields_in_saved_file(self, sm):
        """结构化字段通过 _save_session 保存到 JSON"""
        await sm.create_session("test")
        tc = [{"id": "c1", "name": "run_shell", "arguments": '{"cmd":"ls"}'}]
        await sm.add_message("assistant", "checking", tool_calls=tc)
        await sm._save_session()
        raw = _read_session_file(sm)
        last = raw["messages"][-1]
        assert last["tool_calls"] == tc
        # 不应再有 structured_messages 键
        assert "structured_messages" not in raw


# =============================================================================
# 4. 全链路完整性
# =============================================================================

class TestFullPersistence:
    """add_message → save → load → 数据完整"""

    @pytest.mark.asyncio
    async def test_save_and_reload(self, sm):
        """保存后重载，全部消息和字段完整"""
        sid = await sm.create_session("complex task")
        await sm.add_message(
            "assistant", "let me check",
            tool_calls=[{"id": "c1", "name": "run_shell",
                         "arguments": '{"cmd":"ls"}'}],
            reasoning_content="need to list files",
        )
        await sm.add_message("tool", "file1.py", tool_call_id="c1")
        await sm.add_message("assistant", "done")
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        msgs = sm2.current_messages

        assistants = [m for m in msgs if m.role == "assistant"]
        tools = [m for m in msgs if m.role == "tool"]
        assert len(assistants) >= 2
        assert len(tools) == 1
        assert assistants[0].tool_calls is not None
        assert assistants[0].reasoning_content == "need to list files"
        assert tools[0].tool_call_id == "c1"

    @pytest.mark.asyncio
    async def test_get_messages_returns_all_roles(self, sm):
        """get_messages 返回所有非 system 消息"""
        sid = await sm.create_session("task")
        await sm.add_message("assistant", "plain response")
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        msgs = await sm2.get_messages(include_system=False)
        assert len(msgs) >= 2
        assert msgs[-1]["role"] == "assistant"
        assert msgs[-1]["content"] == "plain response"

    @pytest.mark.asyncio
    async def test_no_system_message_in_session(self, sm):
        """第一条 system 消息不再持久化到 session JSON，current_messages 直接以 user 消息开头"""
        sid = await sm.create_session("task")
        assert sm.current_messages[0].role == "user"
        assert sm.current_messages[0].content == "task"
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        assert sm2.current_messages[0].role == "user"
        assert sm2.current_messages[0].content == "task"


# =============================================================================
# 5. session_index 时间戳兼容
# =============================================================================

class TestIndexTimestampCompat:
    """sessions_index 中时间戳兼容性"""

    @pytest.mark.asyncio
    async def test_sort_with_mixed_formats(self, sm):
        """混合 ISO 8601 和旧版 float 的排序（极端情况）"""
        # 模拟旧版会话（直接从索引字典操作，不走 create_session）
        sid_old = "session_old"
        sm.sessions_index[sid_old] = type('Summary', (), {
            "session_id": sid_old,
            "created_at": 1000000.0,
            "last_activity": 1000000.0,
            "total_messages": 1,
            "total_tokens": 10,
            "task_count": 1,
            "title": "old",
            "status": "active",
        })()
        # 新版会话
        sid_new = await sm.create_session("new")
        sm.sessions_index[sid_new].last_activity = "2026-05-28T10:00:00"

        sessions = await sm.list_sessions()
        # 不应抛异常
        assert len(sessions) >= 2

    @pytest.mark.asyncio
    async def test_current_session_file_compat(self, sm):
        """current_session.txt 兼容 str/float created_at"""
        await sm.create_session("test")
        sm.sessions_index[sm.current_session_id].created_at = "2026-05-28T10:00:00"
        await sm._save_current_session_id()
        # 不应抛异常
        current_file = sm.sessions_dir.parent / "current_session.txt"
        assert current_file.exists()
