"""
测试会话级待办清单隔离 —— 多个会话的待办清单互相独立

覆盖场景：
1. 不同 session 的 add_todo_item 写入各自的 todo 文件，互相不污染
2. 同一 process 内并发两个 session 的 todo 操作不会互相干扰
3. mark_todo_completed 只操作当前 session 的 todo 项
4. get_todo_summary 返回当前 session 的统计，不包含其他 session
5. session_todo_map.json 持久化恢复后，session→todo 映射正确
6. set_session_todo + create_todo_list 绑定工作正常
7. remove_session_todo 清理后不再关联
8. 向后兼容：不传 session_id 时使用 current_todo_file
"""
import sys
import tempfile
import asyncio
import json
from pathlib import Path

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

import pytest
from aacode.utils.todo_manager import TodoManager, get_todo_manager


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


@pytest.fixture
def mgr(project_path):
    return TodoManager(project_path)


class TestSessionTodoIsolation:

    @pytest.mark.asyncio
    async def test_different_sessions_create_different_files(self, mgr):
        f1 = await mgr.create_todo_list("Task A", session_id="session-a")
        f2 = await mgr.create_todo_list("Task B", session_id="session-b")

        assert f1 != f2, f"Session files should be different: {f1} == {f2}"

        file_a = mgr._resolve_todo_file("session-a")
        file_b = mgr._resolve_todo_file("session-b")
        assert file_a != file_b
        assert file_a.exists()
        assert file_b.exists()

    @pytest.mark.asyncio
    async def test_add_todo_item_scoped_to_session(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        await mgr.create_todo_list("Task B", session_id="session-b")

        await mgr.add_todo_item("A-item-1", session_id="session-a")
        await mgr.add_todo_item("A-item-2", session_id="session-a")
        await mgr.add_todo_item("B-item-1", session_id="session-b")

        file_a = mgr._resolve_todo_file("session-a")
        file_b = mgr._resolve_todo_file("session-b")

        content_a = file_a.read_text()
        content_b = file_b.read_text()

        assert "A-item-1" in content_a
        assert "A-item-2" in content_a
        assert "B-item-1" not in content_a, "Session A should not contain B's items"

        assert "B-item-1" in content_b
        assert "A-item-1" not in content_b, "Session B should not contain A's items"

    @pytest.mark.asyncio
    async def test_get_todo_summary_scoped_to_session(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        await mgr.create_todo_list("Task B", session_id="session-b")

        await mgr.add_todo_item("A-1", session_id="session-a")
        await mgr.add_todo_item("A-2", session_id="session-a")
        await mgr.add_todo_item("B-1", session_id="session-b")

        summary_a = await mgr.get_todo_summary(session_id="session-a")
        summary_b = await mgr.get_todo_summary(session_id="session-b")

        assert summary_a["pending_todos"] == 2, f"Session A should have 2 pending, got {summary_a}"
        assert summary_a["total_todos"] == 2

        assert summary_b["pending_todos"] == 1, f"Session B should have 1 pending, got {summary_b}"
        assert summary_b["total_todos"] == 1

        assert summary_a["todo_file"] != summary_b["todo_file"]

    @pytest.mark.asyncio
    async def test_mark_completed_scoped_to_session(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        await mgr.create_todo_list("Task B", session_id="session-b")

        tid_a = await mgr.add_todo_item("A-1", session_id="session-a")
        tid_b = await mgr.add_todo_item("B-1", session_id="session-b")

        await mgr.mark_todo_completed(todo_id=tid_a, session_id="session-a")

        summary_a = await mgr.get_todo_summary(session_id="session-a")
        summary_b = await mgr.get_todo_summary(session_id="session-b")

        assert summary_a["completed_todos"] == 1
        assert summary_a["pending_todos"] == 0

        assert summary_b["completed_todos"] == 0
        assert summary_b["pending_todos"] == 1

    @pytest.mark.asyncio
    async def test_concurrent_sessions_independent_counters(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        await mgr.create_todo_list("Task B", session_id="session-b")

        tid_a1 = await mgr.add_todo_item("A-1", session_id="session-a")
        tid_a2 = await mgr.add_todo_item("A-2", session_id="session-a")
        tid_a3 = await mgr.add_todo_item("A-3", session_id="session-a")

        tid_b1 = await mgr.add_todo_item("B-1", session_id="session-b")
        tid_b2 = await mgr.add_todo_item("B-2", session_id="session-b")

        assert tid_a1 == "t1"
        assert tid_a2 == "t2"
        assert tid_a3 == "t3"
        assert tid_b1 == "t1"
        assert tid_b2 == "t2"

    @pytest.mark.asyncio
    async def test_update_todo_scoped_to_session(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        await mgr.create_todo_list("Task B", session_id="session-b")

        await mgr.add_todo_item("Old item A", session_id="session-a")
        await mgr.add_todo_item("Old item B", session_id="session-b")

        await mgr.update_todo_item("Old item A", "Updated item A", session_id="session-a")
        await mgr.update_todo_item("Old item B", "Updated item B", session_id="session-b")

        file_a = mgr._resolve_todo_file("session-a")
        file_b = mgr._resolve_todo_file("session-b")

        content_a = file_a.read_text()
        content_b = file_b.read_text()

        assert "Updated item A" in content_a
        assert "Old item A" not in content_a
        assert "Updated item B" not in content_a

        assert "Updated item B" in content_b
        assert "Old item B" not in content_b
        assert "Updated item A" not in content_b


class TestSessionTodoMapPersistence:

    @pytest.mark.asyncio
    async def test_map_persisted_and_loaded(self, project_path):
        mgr1 = TodoManager(project_path)
        await mgr1.create_todo_list("Task A", session_id="session-a")
        await mgr1.add_todo_item("Item 1", session_id="session-a")

        map_path = project_path / ".aacode" / "todos" / "session_todo_map.json"
        assert map_path.exists()

        mgr2 = TodoManager(project_path)
        file = mgr2._resolve_todo_file("session-a")
        assert file is not None
        assert file.exists()
        assert "Item 1" in file.read_text()

    @pytest.mark.asyncio
    async def test_counters_persisted_and_loaded(self, project_path):
        mgr1 = TodoManager(project_path)
        await mgr1.create_todo_list("Task A", session_id="session-a")
        await mgr1.add_todo_item("Item 1", session_id="session-a")
        await mgr1.add_todo_item("Item 2", session_id="session-a")

        mgr2 = TodoManager(project_path)
        tid = await mgr2.add_todo_item("Item 3", session_id="session-a")
        assert tid == "t3", f"Counter should resume from 3, got {tid}"


class TestSetSessionTodo:

    @pytest.mark.asyncio
    async def test_set_session_todo_binds_existing_file(self, mgr):
        rel_path = await mgr.create_todo_list("Task X")
        f_path = mgr.project_path / rel_path
        assert f_path.exists()
        mgr.set_session_todo("session-x", f_path)

        assert mgr._resolve_todo_file("session-x") == f_path
        assert mgr._resolve_counter("session-x") == 0

    @pytest.mark.asyncio
    async def test_remove_session_todo(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        assert mgr._resolve_todo_file("session-a") is not None

        mgr.remove_session_todo("session-a")
        assert mgr._resolve_todo_file("session-a") is None


class TestBackwardCompatibility:

    @pytest.mark.asyncio
    async def test_no_session_id_uses_current_todo_file(self, mgr):
        f = await mgr.create_todo_list("Legacy Task")
        await mgr.add_todo_item("Legacy Item")

        summary = await mgr.get_todo_summary()
        assert summary["pending_todos"] == 1
        assert summary["total_todos"] == 1

    @pytest.mark.asyncio
    async def test_mixed_session_and_no_session(self, mgr):
        await mgr.create_todo_list("Task A", session_id="session-a")
        await mgr.add_todo_item("A-1", session_id="session-a")

        f_legacy = await mgr.create_todo_list("Legacy")
        await mgr.add_todo_item("Legacy-1")

        summary_a = await mgr.get_todo_summary(session_id="session-a")
        assert summary_a["pending_todos"] == 1
        assert "A-1" in mgr._resolve_todo_file("session-a").read_text()

        summary_legacy = await mgr.get_todo_summary()
        assert summary_legacy["pending_todos"] == 1


class TestGetTodoManagerSingleton:

    def test_same_project_returns_same_instance(self, project_path):
        mgr1 = get_todo_manager(project_path)
        mgr2 = get_todo_manager(project_path)
        assert mgr1 is mgr2

    def test_different_projects_return_different_instances(self):
        with tempfile.TemporaryDirectory() as td1, tempfile.TemporaryDirectory() as td2:
            mgr1 = get_todo_manager(Path(td1))
            mgr2 = get_todo_manager(Path(td2))
            assert mgr1 is not mgr2
