# -*- coding: utf-8 -*-
"""CG UndoCommand /undo 命令测试（Phase 8 Tier 2）"""
import os
import time
from pathlib import Path

import pytest

from abyss.commands.undo import (
    handle_undo,
    init_history_service,
    get_history_service,
)
from abyss.history import FileHistoryService


class _FakeSession:
    """测试用的最小 Session。"""

    def __init__(self, session_id: str = "test-session"):
        self.session_id = session_id
        self.messages = []


@pytest.fixture(autouse=True)
def reset_history_service():
    """每个测试前后重置全局单例。"""
    init_history_service(FileHistoryService(storage_dir="__test_reset__"))
    yield
    init_history_service(FileHistoryService(storage_dir="__test_reset__"))


class TestHandleUndo:
    """handle_undo 主流程测试"""

    def test_no_history_service_returns_error(self):
        # 临时清空全局 service
        import abyss.commands.undo as undo_mod
        old = undo_mod._HISTORY_SERVICE
        undo_mod._HISTORY_SERVICE = None
        try:
            session = _FakeSession()
            result = handle_undo(session)
            assert "[ERR]" in result
        finally:
            undo_mod._HISTORY_SERVICE = old

    def test_no_session_id_falls_back_to_default(self, tmp_path):
        """session 无 session_id 时应回退到 "default"，与 main.py:744 一致。

        痛点：Session 类没有 session_id 属性，main.py 在 begin_transaction
        时用 `or "default"` 兜底。undo 命令必须用同样的 fallback 才能查到
        那些事务。
        """
        svc = FileHistoryService(storage_dir=str(tmp_path / "hist"))
        init_history_service(svc)
        # 用 "default" session_id 创建一个事务（模拟 main.py 的行为）
        target = tmp_path / "file.py"
        target.write_text("cur", encoding="utf-8")
        tid = svc.begin_transaction("default", "m1")
        svc.record_file_change(tid, str(target), "old")
        svc.commit_transaction(tid)

        # session 无 session_id 属性 → getattr 返回 "" → fallback 到 "default"
        class _SessionNoId:
            messages = []
        result = handle_undo(_SessionNoId())
        assert "[OK]" in result
        assert target.read_text(encoding="utf-8") == "old"

    def test_no_transactions_returns_info(self, tmp_path):
        svc = FileHistoryService(storage_dir=str(tmp_path))
        init_history_service(svc)
        session = _FakeSession(session_id="s1")
        result = handle_undo(session)
        assert "[INFO]" in result
        assert "无可撤销" in result

    def test_undo_finds_latest_transaction(self, tmp_path):
        svc = FileHistoryService(storage_dir=str(tmp_path / "hist"))
        init_history_service(svc)
        # 创建一个事务
        target = tmp_path / "file.py"
        target.write_text("current", encoding="utf-8")
        tid = svc.begin_transaction("s1", "m1")
        svc.record_file_change(tid, str(target), "old")
        svc.commit_transaction(tid)

        session = _FakeSession(session_id="s1")
        result = handle_undo(session)
        assert "[OK]" in result
        assert target.read_text(encoding="utf-8") == "old"

    def test_undo_lists_affected_files(self, tmp_path):
        svc = FileHistoryService(storage_dir=str(tmp_path / "hist"))
        init_history_service(svc)
        a = tmp_path / "a.py"
        b = tmp_path / "b.py"
        a.write_text("a-cur", encoding="utf-8")
        b.write_text("b-cur", encoding="utf-8")

        tid = svc.begin_transaction("s1", "m1")
        svc.record_file_change(tid, str(a), "a-old")
        svc.record_file_change(tid, str(b), "b-old")
        svc.commit_transaction(tid)

        session = _FakeSession(session_id="s1")
        result = handle_undo(session)
        assert "影响文件 2 个" in result
        assert str(a) in result
        assert str(b) in result

    def test_undo_skips_already_rewound(self, tmp_path):
        svc = FileHistoryService(storage_dir=str(tmp_path / "hist"))
        init_history_service(svc)
        # 两个事务：旧的已 rewind，新的可 rewind
        old_target = tmp_path / "old.py"
        old_target.write_text("cur", encoding="utf-8")
        tid1 = svc.begin_transaction("s1", "m1")
        svc.record_file_change(tid1, str(old_target), "old-content")
        svc.commit_transaction(tid1)
        time.sleep(0.01)

        new_target = tmp_path / "new.py"
        new_target.write_text("cur", encoding="utf-8")
        tid2 = svc.begin_transaction("s1", "m2")
        svc.record_file_change(tid2, str(new_target), "new-old")
        svc.commit_transaction(tid2)

        # 第一次 undo 应撤销最新（tid2）
        session = _FakeSession(session_id="s1")
        r1 = handle_undo(session)
        assert "[OK]" in r1
        assert new_target.read_text(encoding="utf-8") == "new-old"

        # 第二次 undo 应撤销 tid1
        r2 = handle_undo(session)
        assert "[OK]" in r2
        assert old_target.read_text(encoding="utf-8") == "old-content"

    def test_undo_scoped_to_session(self, tmp_path):
        svc = FileHistoryService(storage_dir=str(tmp_path / "hist"))
        init_history_service(svc)

        # session A 的事务
        a_target = tmp_path / "a.py"
        a_target.write_text("cur", encoding="utf-8")
        tid_a = svc.begin_transaction("sess-a", "m1")
        svc.record_file_change(tid_a, str(a_target), "a-old")
        svc.commit_transaction(tid_a)

        # session B 的事务
        b_target = tmp_path / "b.py"
        b_target.write_text("cur", encoding="utf-8")
        tid_b = svc.begin_transaction("sess-b", "m1")
        svc.record_file_change(tid_b, str(b_target), "b-old")
        svc.commit_transaction(tid_b)

        # session A 的 undo 应只影响 a.py
        session_a = _FakeSession(session_id="sess-a")
        result = handle_undo(session_a)
        assert "[OK]" in result
        assert str(a_target) in result
        assert str(b_target) not in result
        assert a_target.read_text(encoding="utf-8") == "a-old"
        # b.py 不应被改动
        assert b_target.read_text(encoding="utf-8") == "cur"


class TestInitAndGetService:
    """init_history_service / get_history_service 测试"""

    def test_init_and_get_roundtrip(self, tmp_path):
        svc = FileHistoryService(storage_dir=str(tmp_path))
        init_history_service(svc)
        assert get_history_service() is svc

    def test_get_returns_none_initially(self):
        import abyss.commands.undo as undo_mod
        old = undo_mod._HISTORY_SERVICE
        undo_mod._HISTORY_SERVICE = None
        try:
            assert get_history_service() is None
        finally:
            undo_mod._HISTORY_SERVICE = old
