"""
单轨结构化格式（A 方案）专项测试

覆盖：
1. 新创建会话不产生 structured_messages 键
2. tool_calls / reasoning_content / tool_call_id 全部存入 messages
3. 旧版双轨文件加载兼容（structured_messages → current_messages 转换）
4. "✅ Task completed" 标记正常保存和加载
5. 多轮 full cycle：add → save → reload → get_messages 数据完整
6. 文件格式验证：不含 structured_messages 键
"""
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


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


def _read_session_file(sm):
    f = sm.sessions_dir / f"{sm.current_session_id}.json"
    with open(f, "r") as fh:
        return json.load(fh)


class TestSingleTrackNoDualKey:
    """新格式不会产生 structured_messages 键"""

    @pytest.mark.asyncio
    async def test_new_session_no_structured_key(self, sm):
        sid = await sm.create_session("test")
        raw = _read_session_file(sm)
        assert "messages" in raw
        assert "structured_messages" not in raw

    @pytest.mark.asyncio
    async def test_after_add_message_no_structured_key(self, sm):
        await sm.create_session("test")
        await sm.add_message("assistant", "hello")
        raw = _read_session_file(sm)
        assert "structured_messages" not in raw

    @pytest.mark.asyncio
    async def test_after_multiple_saves_no_structured_key(self, sm):
        await sm.create_session("test")
        for i in range(5):
            await sm.add_message("user", f"msg {i}")
            raw = _read_session_file(sm)
            assert "structured_messages" not in raw, \
                f"structured_messages appeared after save {i}"


class TestStructuredFieldsInMessages:
    """结构化字段全部存入 messages 数组"""

    @pytest.mark.asyncio
    async def test_tool_calls_in_messages(self, sm):
        await sm.create_session("test")
        tc = [{"id": "c1", "name": "run_shell", "arguments": '{"cmd":"ls"}'}]
        await sm.add_message("assistant", "checking", tool_calls=tc)

        raw = _read_session_file(sm)
        last = raw["messages"][-1]
        assert last["tool_calls"] == tc
        assert "structured_messages" not in raw

    @pytest.mark.asyncio
    async def test_reasoning_content_in_messages(self, sm):
        await sm.create_session("test")
        await sm.add_message("assistant", "checking", reasoning_content="thinking...")

        raw = _read_session_file(sm)
        last = raw["messages"][-1]
        assert last["reasoning_content"] == "thinking..."

    @pytest.mark.asyncio
    async def test_tool_call_id_in_messages(self, sm):
        await sm.create_session("test")
        await sm.add_message("tool", "output", tool_call_id="c1")

        raw = _read_session_file(sm)
        last = raw["messages"][-1]
        assert last["role"] == "tool"
        assert last["tool_call_id"] == "c1"

    @pytest.mark.asyncio
    async def test_timestamp_and_tokens_on_each_message(self, sm):
        await sm.create_session("test")
        await sm.add_message("user", "hello")

        raw = _read_session_file(sm)
        for msg in raw["messages"]:
            assert "timestamp" in msg, f"Missing timestamp in {msg['role']}"
            assert "tokens" in msg, f"Missing tokens in {msg['role']}"
            assert isinstance(msg["timestamp"], str), \
                f"timestamp should be str, got {type(msg['timestamp'])}"
            assert isinstance(msg["tokens"], int), \
                f"tokens should be int, got {type(msg['tokens'])}"


class TestOldDualTrackCompatibility:
    """旧版双轨文件加载兼容"""

    @pytest.mark.asyncio
    async def test_load_old_dual_track_file(self, sm):
        """模拟旧版双轨文件，structured_messages 优先"""
        sid = await sm.create_session("compat")
        # 构造旧版格式：messages（collapsed）+ structured_messages
        await sm._save_session()
        raw = _read_session_file(sm)
        raw["structured_messages"] = [
            {"role": "user", "content": "q1"},
            {"role": "assistant", "content": "a1", "tool_calls": [{"name": "search"}],
             "reasoning_content": "thinking..."},
            {"role": "tool", "content": "result", "tool_call_id": "c1"},
        ]
        with open(sm.sessions_dir / f"{sid}.json", "w") as f:
            json.dump(raw, f)

        # 重新加载 → structured_messages 应被导入到 current_messages
        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)

        msgs = await sm2.get_messages(include_system=False)
        assert len(msgs) >= 3
        roles = [m["role"] for m in msgs]
        assert "user" in roles
        assert "assistant" in roles
        assert "tool" in roles

        assistant_msgs = [m for m in msgs if m["role"] == "assistant"]
        assert assistant_msgs[0]["tool_calls"] == [{"name": "search"}]
        assert assistant_msgs[0]["reasoning_content"] == "thinking..."

        # 保存后不应再有 structured_messages 键
        await sm2._save_session()
        saved = json.loads((sm2.sessions_dir / f"{sid}.json").read_text())
        assert "structured_messages" not in saved

    @pytest.mark.asyncio
    async def test_load_old_no_structured_fallback(self, sm):
        """旧版文件无 structured_messages，退回到 messages"""
        sid = await sm.create_session("compat")
        # 删掉 structured_messages 键，模拟纯旧格式文件
        raw = _read_session_file(sm)
        raw.pop("structured_messages", None)
        with open(sm.sessions_dir / f"{sid}.json", "w") as f:
            json.dump(raw, f)

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        msgs = await sm2.get_messages(include_system=False)
        # 应至少包含用户消息
        user_msgs = [m for m in msgs if m["role"] == "user"]
        assert len(user_msgs) >= 1

    @pytest.mark.asyncio
    async def test_empty_structured_messages_fallback(self, sm):
        """structured_messages 为空列表时退回到 messages"""
        sid = await sm.create_session("compat")
        raw = _read_session_file(sm)
        raw["structured_messages"] = []  # 空列表
        with open(sm.sessions_dir / f"{sid}.json", "w") as f:
            json.dump(raw, f)

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        msgs = await sm2.get_messages(include_system=False)
        # 不应为空
        assert len(msgs) >= 1


class TestTaskCompletedMarker:
    """✅ Task completed 标记正常保存和加载"""

    @pytest.mark.asyncio
    async def test_task_completed_saved(self, sm):
        await sm.create_session("test")
        await sm.add_message("user", "do something")
        await sm.add_message("assistant", "✅ Task completed")

        raw = _read_session_file(sm)
        last = raw["messages"][-1]
        assert last["content"] == "✅ Task completed"
        assert last["role"] == "assistant"

    @pytest.mark.asyncio
    async def test_task_completed_survives_reload(self, sm):
        sid = await sm.create_session("test")
        await sm.add_message("user", "do something")
        await sm.add_message("assistant", "✅ Task completed")
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        msgs = await sm2.get_messages(include_system=False)
        contents = [m["content"] for m in msgs]
        assert any("✅ Task completed" in c for c in contents)


class TestFullCycle:
    """完整的保存→重载→获取 周期"""

    @pytest.mark.asyncio
    async def test_full_cycle_with_structured_fields(self, sm):
        sid = await sm.create_session("cycle test")

        await sm.add_message("user", "list files")
        await sm.add_message(
            "assistant", "I'll check",
            tool_calls=[{"id": "c1", "name": "run_shell",
                         "arguments": '{"command":"ls"}'}],
            reasoning_content="need to list directory",
        )
        await sm.add_message("tool", "file1.py\nfile2.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 = await sm2.get_messages(include_system=False)

        # 断言角色
        roles = [m["role"] for m in msgs]
        assert "user" in roles
        assert "assistant" in roles
        assert "tool" in roles

        # 断言字段
        tool_msg = [m for m in msgs if m["role"] == "tool"][0]
        assert tool_msg["tool_call_id"] == "c1"

        assistant_msgs = [m for m in msgs if m["role"] == "assistant"]
        assistant_with_tc = [m for m in assistant_msgs if "tool_calls" in m]
        assert len(assistant_with_tc) >= 1
        assert assistant_with_tc[0]["tool_calls"][0]["name"] == "run_shell"
        assert assistant_with_tc[0]["reasoning_content"] == "need to list directory"

        # 断言文件格式
        raw = json.loads((sm2.sessions_dir / f"{sid}.json").read_text())
        assert "structured_messages" not in raw
        assert len(raw["messages"]) >= 5


class TestGetMessagesConsistency:
    """get_messages 返回格式一致性"""

    @pytest.mark.asyncio
    async def test_get_messages_include_system(self, sm):
        await sm.create_session("test")
        await sm.add_message("user", "hello")
        msgs = await sm.get_messages(include_system=True)
        roles = [m["role"] for m in msgs]
        assert "system" in roles

    @pytest.mark.asyncio
    async def test_get_messages_exclude_system(self, sm):
        await sm.create_session("test")
        await sm.add_message("user", "hello")
        msgs = await sm.get_messages(include_system=False)
        roles = [m["role"] for m in msgs]
        assert "system" not in roles

    @pytest.mark.asyncio
    async def test_get_messages_from_specific_session(self, sm):
        sid1 = await sm.create_session("first")
        await sm.add_message("user", "msg in first")

        # 切到第二个 session 后，get_messages 指定第一个
        await sm.create_session("second")
        msgs = await sm.get_messages(sid1, include_system=False)
        contents = [m["content"] for m in msgs]
        assert any("msg in first" in c for c in contents)


class TestNoSideEffects:
    """确认无意外的副作用"""

    @pytest.mark.asyncio
    async def test_remove_metadata_does_not_affect_save(self, sm):
        """metadata 字段已移除，保存时不应出现"""
        await sm.create_session("test")
        await sm.add_message("user", "hello")
        raw = _read_session_file(sm)
        for msg in raw["messages"]:
            assert "metadata" not in msg, \
                f"metadata should not exist in saved messages, found in {msg['role']}"

    @pytest.mark.asyncio
    async def test_delete_session_works(self, sm):
        sid = await sm.create_session("test")
        await sm.add_message("user", "hello")
        ok = await sm.delete_session(sid)
        assert ok
        assert sm.current_session_id is None

    @pytest.mark.asyncio
    async def test_list_sessions_order(self, sm):
        await sm.create_session("first")
        await sm.create_session("second")
        sessions = await sm.list_sessions()
        assert len(sessions) == 2


class TestResponseWithReasoning:
    """assistant 同时有 reasoning_content + content（总结文本）的保存与读取"""

    @pytest.mark.asyncio
    async def test_reasoning_and_content_both_preserved(self, sm):
        sid = await sm.create_session("test")
        sm.current_messages.append(SessionMessage(
            role="assistant",
            content="💭 Thinking process:\nthinking in english...\n\n---\n\n## 总结\n\n这是最终总结内容",
            timestamp="2026-01-01T00:00:00",
            reasoning_content="thinking in english...",
        ))
        sm.current_messages.append(SessionMessage(
            role="assistant", content="✅ Task completed",
            timestamp="2026-01-01T00:00:01", tokens=4,
        ))
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        msgs = await sm2.get_messages(include_system=False)

        assistant_msgs = [m for m in msgs if m["role"] == "assistant"]
        last = assistant_msgs[-2]  # 倒数第二条（不是 ✅ Task completed）
        assert last["reasoning_content"] == "thinking in english..."
        assert "最终总结内容" in last["content"]
        assert "tool_calls" not in last
