"""
测试 system 消息重构：不再持久化确定性 system prompt 到 session JSON。

覆盖范围：
1. create_session 不再创建占位 system 消息
2. get_messages(include_system=False) 正确过滤 system
3. get_messages(include_system=True) 仍包含手动添加的 system（压缩摘要等）
4. 向后兼容：旧版 session JSON 含占位 system 时正确加载和过滤
5. _compact_context 在无/有旧 system 时均正确
6. 多轮任务：system 不会进入 history_messages
7. has_user_msg 检查适配无 system 的 current_messages
8. execute() 构建的 system prompt 包含 todo_section
9. 恢复提示 system 消息注入不受影响
"""
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 tmp_project():
    with tempfile.TemporaryDirectory() as td:
        yield Path(td)


@pytest.fixture
def sm(tmp_project):
    return SessionManager(tmp_project)


def _read_session_json(sm):
    f = sm.sessions_dir / f"{sm.current_session_id}.json"
    with open(f, encoding="utf-8") as fh:
        return json.load(fh)


# =============================================================================
# 1. create_session: 不再创建占位 system 消息
# =============================================================================

class TestCreateSessionNoSystem:
    @pytest.mark.asyncio
    async def test_session_starts_with_user_only(self, sm):
        sid = await sm.create_session("my task")
        assert len(sm.current_messages) == 1
        assert sm.current_messages[0].role == "user"
        assert sm.current_messages[0].content == "my task"

    @pytest.mark.asyncio
    async def test_empty_task_creates_empty_messages(self, sm):
        sid = await sm.create_session("")
        assert len(sm.current_messages) == 0

        sid2 = await sm.create_session("   ")
        assert len(sm.current_messages) == 0

    @pytest.mark.asyncio
    async def test_no_system_role_in_initial_messages(self, sm):
        sid = await sm.create_session("task")
        roles = [m.role for m in sm.current_messages]
        assert "system" not in roles

    @pytest.mark.asyncio
    async def test_json_file_no_system_message(self, sm):
        sid = await sm.create_session("task")
        data = _read_session_json(sm)
        roles = [m["role"] for m in data["messages"]]
        assert "system" not in roles
        assert roles == ["user"]

    @pytest.mark.asyncio
    async def test_save_reload_preserves_no_system(self, sm):
        sid = await sm.create_session("task")
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sid)
        roles = [m.role for m in sm2.current_messages]
        assert "system" not in roles
        assert len(sm2.current_messages) == 1
        assert sm2.current_messages[0].content == "task"


# =============================================================================
# 2. get_messages: include_system 过滤行为
# =============================================================================

class TestGetMessagesFiltering:
    @pytest.mark.asyncio
    async def test_exclude_system_returns_no_system(self, sm):
        await sm.create_session("task")
        await sm.add_message("assistant", "response")
        msgs = await sm.get_messages(include_system=False)
        roles = [m["role"] for m in msgs]
        assert "system" not in roles
        assert roles == ["user", "assistant"]

    @pytest.mark.asyncio
    async def test_include_system_shows_compaction_summary(self, sm):
        """手动添加的 system 消息（如压缩摘要）在 include_system=True 时可见"""
        await sm.create_session("task")
        await sm.add_message("system", "Context summary: previous 10 messages compressed")
        msgs = await sm.get_messages(include_system=True)
        roles = [m["role"] for m in msgs]
        assert "system" in roles
        assert roles.count("system") == 1

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


# =============================================================================
# 3. 向后兼容：旧版 session JSON 含占位 system
# =============================================================================

class TestBackwardCompatOldSystemPlaceholder:
    @pytest.mark.asyncio
    async def test_load_old_session_with_system_placeholder(self, sm):
        """模拟旧版 session JSON: current_messages[0] 是占位 system"""
        session_id = "session_20260501_000000_abcdef01"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "You are an AI coding assistant. Please work as follows:\n1. First plan the task steps\n2. Execute and verify step by step\n3. Ensure code quality and maintainability",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 10,
                },
                {
                    "role": "user",
                    "content": "old task",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 2,
                },
                {
                    "role": "assistant",
                    "content": "old response",
                    "timestamp": "2026-05-01T00:00:02",
                    "tokens": 2,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:02",
                "total_messages": 3,
                "total_tokens": 14,
                "task_count": 1,
                "title": "old task",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        assert len(sm.current_messages) == 3
        assert sm.current_messages[0].role == "system"  # 旧占位保留
        assert sm.current_messages[1].role == "user"
        assert sm.current_messages[2].role == "assistant"

    @pytest.mark.asyncio
    async def test_old_system_filtered_from_llm_context(self, sm):
        """旧占位 system 不会传给 LLM"""
        session_id = "session_20260501_000001_abcdef02"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "OLD PLACEHOLDER SYSTEM PROMPT",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 5,
                },
                {
                    "role": "user",
                    "content": "old task",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 2,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:01",
                "total_messages": 2,
                "total_tokens": 7,
                "task_count": 1,
                "title": "old task",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        msgs = await sm.get_messages(include_system=False)
        roles = [m["role"] for m in msgs]
        assert "system" not in roles
        assert roles == ["user"]
        assert msgs[0]["content"] == "old task"

    @pytest.mark.asyncio
    async def test_old_system_does_not_block_add_message(self, sm):
        """旧占位 system 不影响 has_user_msg 逻辑"""
        session_id = "session_20260501_000002_abcdef03"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "OLD PLACEHOLDER",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 1,
                },
                {
                    "role": "user",
                    "content": "old task",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 2,
                },
                {
                    "role": "assistant",
                    "content": "done",
                    "timestamp": "2026-05-01T00:00:02",
                    "tokens": 1,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:02",
                "total_messages": 3,
                "total_tokens": 4,
                "task_count": 1,
                "title": "old task",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        # 模拟 has_user_msg: last 是 assistant，应该允许 add new user
        last_role = sm.current_messages[-1].role
        assert last_role == "assistant"
        # 新任务可以正常添加
        ok = await sm.add_message("user", "new task")
        assert ok
        assert len(sm.current_messages) == 4

    @pytest.mark.asyncio
    async def test_old_system_persists_across_save_reload(self, sm):
        """旧占位 system 在保存/重载后仍保留（不主动删除用户数据）"""
        session_id = "session_20260501_000003_abcdef04"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "OLD PLACEHOLDER",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 1,
                },
                {
                    "role": "user",
                    "content": "task",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 1,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:01",
                "total_messages": 2,
                "total_tokens": 2,
                "task_count": 1,
                "title": "task",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        await sm.add_message("assistant", "new response")
        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(session_id)
        assert len(sm2.current_messages) == 3
        assert sm2.current_messages[0].role == "system"  # 旧占位保留
        assert sm2.current_messages[1].role == "user"
        assert sm2.current_messages[2].role == "assistant"


# =============================================================================
# 4. _compact_context 兼容性
# =============================================================================

class TestCompactContextCompatibility:
    @pytest.mark.asyncio
    async def test_compact_without_system(self, sm):
        """_compact_context 不再修改 current_messages（非破坏性）。"""
        await sm.create_session("task")
        for i in range(10):
            await sm.add_message("assistant", f"response {i}")

        original_len = len(sm.current_messages)
        assert original_len == 11
        await sm._compact_context()
        # 非破坏性：current_messages 不修改
        assert len(sm.current_messages) == original_len

    @pytest.mark.asyncio
    async def test_compact_preserves_compaction_system_messages(self, sm):
        """多次调用 _compact_context 不修改 current_messages（非破坏性）。"""
        await sm.create_session("task")
        for i in range(10):
            await sm.add_message("assistant", f"msg {i}")

        await sm._compact_context()
        first_len = len(sm.current_messages)

        for i in range(10):
            await sm.add_message("assistant", f"more msg {i}")

        await sm._compact_context()
        # 非破坏性：消息数量只增不减
        assert len(sm.current_messages) > first_len

    @pytest.mark.asyncio
    async def test_compact_with_old_system_placeholder(self, sm):
        """含旧占位 system 时，压缩不破坏现有数据。"""
        session_id = "session_20260501_000010_compact01"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "OLD PLACEHOLDER PROMPT",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 3,
                },
                {
                    "role": "user",
                    "content": "task",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 1,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:01",
                "total_messages": 2,
                "total_tokens": 4,
                "task_count": 1,
                "title": "task",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        for i in range(10):
            await sm.add_message("assistant", f"msg {i}")
        await sm._compact_context()

        system_msgs = [m for m in sm.current_messages if m.role == "system"]
        # 非破坏性：仅保留旧占位 system，不追加压缩摘要
        assert any("OLD PLACEHOLDER" in m.content for m in system_msgs)
        # 旧占位仍然在，数据未被破坏


# =============================================================================
# 5. has_user_msg 检查逻辑
# =============================================================================

class TestHasUserMsgCheck:
    @pytest.mark.asyncio
    async def test_duplicate_user_task_not_added(self, sm):
        """current_messages 最后一条是相同 user task 时，不再重复添加"""
        await sm.create_session("my task")
        # 模拟 execute() 中的 has_user_msg 检查
        has_user_msg = (
            len(sm.current_messages) > 0
            and sm.current_messages[-1].role == "user"
            and sm.current_messages[-1].content == "my task"
        )
        assert has_user_msg is True

    @pytest.mark.asyncio
    async def test_new_task_added_when_last_is_assistant(self, sm):
        """上一条是 assistant 时，新 task 应被添加"""
        await sm.create_session("task1")
        await sm.add_message("assistant", "response")
        has_user_msg = (
            len(sm.current_messages) > 0
            and sm.current_messages[-1].role == "user"
            and sm.current_messages[-1].content == "task2"
        )
        assert has_user_msg is False  # last is assistant, not user

    @pytest.mark.asyncio
    async def test_handle_empty_current_messages(self, sm):
        """current_messages 为空时 has_user_msg = False"""
        sm.current_session_id = "dummy"
        sm.current_messages = []
        has_user_msg = (
            len(sm.current_messages) > 0
            and sm.current_messages[-1].role == "user"
        )
        assert has_user_msg is False


# =============================================================================
# 6. 多轮任务：system 不进入 history
# =============================================================================

class TestMultiTurnNoSystemInHistory:
    @pytest.mark.asyncio
    async def test_history_messages_exclude_system(self, sm):
        """模拟多轮任务：get_messages(include_system=False) 不含 system"""
        await sm.create_session("task1")
        await sm.add_message("assistant", "response1")
        await sm.add_message("user", "task2")
        await sm.add_message("assistant", "response2")

        history = await sm.get_messages(include_system=False)
        roles = [m["role"] for m in history]
        assert "system" not in roles
        assert len(history) == 4

    @pytest.mark.asyncio
    async def test_history_from_old_session_excludes_system(self, sm):
        """从旧版 session 加载后，history 不含旧占位 system"""
        session_id = "session_20260501_000004_history01"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "OLD SYSTEM PROMPT",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 3,
                },
                {
                    "role": "user",
                    "content": "task1",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 1,
                },
                {
                    "role": "assistant",
                    "content": "response",
                    "timestamp": "2026-05-01T00:00:02",
                    "tokens": 1,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:02",
                "total_messages": 3,
                "total_tokens": 5,
                "task_count": 1,
                "title": "task1",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        history = await sm.get_messages(include_system=False)
        roles = [m["role"] for m in history]
        assert "system" not in roles
        assert roles == ["user", "assistant"]


# =============================================================================
# 7. 恢复提示 system 消息注入
# =============================================================================

class TestRecoveryHintInjection:
    def test_recovery_hint_appended_when_last_is_tool(self):
        """history 最后一条是 tool 时，注入恢复提示 system 消息"""
        history_messages = [
            {"role": "user", "content": "task"},
            {"role": "assistant", "content": "thinking", "tool_calls": [
                {"id": "call_1", "function": {"name": "run_shell", "arguments": "{}"}}
            ]},
            {"role": "tool", "tool_call_id": "call_1", "content": "result"},
        ]
        if history_messages and history_messages[-1].get("role") == "tool":
            recovery_hint = "[系统提示] 上一轮任务在工具执行后中断。请直接基于已有的工具执行结果和历史上下文继续。"
            history_messages.append({"role": "system", "content": recovery_hint})

        assert history_messages[-1]["role"] == "system"
        assert "工具执行后中断" in history_messages[-1]["content"]

    def test_no_recovery_hint_when_last_is_assistant(self):
        """history 最后一条不是 tool 时不注入"""
        history_messages = [
            {"role": "user", "content": "task"},
            {"role": "assistant", "content": "done"},
        ]
        hint_added = False
        if history_messages and history_messages[-1].get("role") == "tool":
            history_messages.append({"role": "system", "content": "hint"})
            hint_added = True

        assert hint_added is False
        assert len(history_messages) == 2

    def test_empty_history_no_crash(self):
        """空 history 不崩溃"""
        history_messages = []
        if history_messages and history_messages[-1].get("role") == "tool":
            history_messages.append({"role": "system", "content": "hint"})
        assert len(history_messages) == 0


# =============================================================================
# 8. get_conversation_history 跳过 system
# =============================================================================

class TestConversationHistorySkipsSystem:
    @pytest.mark.asyncio
    async def test_history_display_skips_system(self, sm):
        await sm.create_session("task")
        await sm.add_message("system", "compaction summary")
        await sm.add_message("assistant", "response")

        text = await sm.get_conversation_history(max_length=10)
        assert "compaction summary" not in text
        assert "task" in text or "response" in text

    @pytest.mark.asyncio
    async def test_history_display_with_old_placeholder(self, sm):
        """旧占位 system 也不会出现在对话历史显示中"""
        session_id = "session_20260501_000005_display01"
        old_data = {
            "session_id": session_id,
            "created_at": "2026-05-01T00:00:00",
            "messages": [
                {
                    "role": "system",
                    "content": "OLD PLACEHOLDER",
                    "timestamp": "2026-05-01T00:00:00",
                    "tokens": 1,
                },
                {
                    "role": "user",
                    "content": "visible task",
                    "timestamp": "2026-05-01T00:00:01",
                    "tokens": 1,
                },
            ],
        }
        session_file = sm.sessions_dir / f"{session_id}.json"
        session_file.write_text(json.dumps(old_data, ensure_ascii=False))

        sm.sessions_index[session_id] = type(
            "SessionSummary",
            (),
            {
                "session_id": session_id,
                "created_at": "2026-05-01T00:00:00",
                "last_activity": "2026-05-01T00:00:01",
                "total_messages": 2,
                "total_tokens": 2,
                "task_count": 1,
                "title": "task",
                "status": "active",
            },
        )()

        await sm.switch_session(session_id)
        text = await sm.get_conversation_history(max_length=10)
        assert "OLD PLACEHOLDER" not in text
        assert "visible task" in text


# =============================================================================
# 9. SessionMessage 数据结构不变
# =============================================================================

class TestSessionMessageFields:
    @pytest.mark.asyncio
    async def test_user_message_fields(self, sm):
        await sm.create_session("task with fields")
        msg = sm.current_messages[0]
        assert msg.role == "user"
        assert msg.content == "task with fields"
        assert msg.timestamp is not None
        assert isinstance(msg.tokens, int)
        assert msg.tool_calls is None
        assert msg.tool_call_id is None

    @pytest.mark.asyncio
    async def test_assistant_message_with_tool_calls(self, sm):
        await sm.create_session("task")
        await sm.add_message(
            "assistant",
            "calling tool",
            tool_calls=[{"id": "c1", "function": {"name": "test", "arguments": "{}"}}],
        )
        msg = sm.current_messages[-1]
        assert msg.role == "assistant"
        assert msg.tool_calls is not None
        assert len(msg.tool_calls) == 1

    @pytest.mark.asyncio
    async def test_tool_message_with_call_id(self, sm):
        await sm.create_session("task")
        await sm.add_message("tool", "result", tool_call_id="c1")
        msg = sm.current_messages[-1]
        assert msg.role == "tool"
        assert msg.tool_call_id == "c1"


# =============================================================================
# 10. session JSON 完整序列化回环
# =============================================================================

class TestSessionJsonRoundtrip:
    @pytest.mark.asyncio
    async def test_roundtrip_preserves_all_message_types(self, sm):
        await sm.create_session("task1")
        await sm.add_message("assistant", "response")
        await sm.add_message(
            "assistant",
            "tool call",
            tool_calls=[{"id": "c1", "function": {"name": "run", "arguments": "{}"}}],
        )
        await sm.add_message("tool", "result", tool_call_id="c1")
        await sm.add_message("user", "task2")
        await sm.add_message("system", "compaction summary")

        await sm._save_session()

        sm2 = SessionManager(sm.project_path)
        await sm2.switch_session(sm.current_session_id)

        assert len(sm2.current_messages) == 6
        assert [m.role for m in sm2.current_messages] == [
            "user", "assistant", "assistant", "tool", "user", "system"
        ]
        # tool_calls preserved
        assert sm2.current_messages[2].tool_calls is not None
        # tool_call_id preserved
        assert sm2.current_messages[3].tool_call_id == "c1"
