# -*- coding: utf-8 -*-
"""CH CompactCommand 测试（Phase 8 Tier 2）

借鉴 zhikuncode CompactCommand.java：
- /compact [custom_instruction] 手动触发上下文压缩
- 调用现有 SimpleTruncationEngine.compress()
- 显示压缩前后 token 数对比
- 保留 system message 不变式
"""
from unittest.mock import MagicMock, patch

import pytest

from abyss.commands.compact import handle_compact


def _make_messages(n: int = 20) -> list:
    """构造 n 条消息的会话"""
    msgs = [{"role": "system", "content": "you are an assistant"}]
    for i in range(n - 1):
        msgs.append({"role": "user" if i % 2 == 0 else "assistant", "content": f"msg {i}" * 10})
    return msgs


class TestHandleCompact:
    """handle_compact 测试"""

    def test_returns_success_message(self):
        """返回包含节省 token 数的成功消息"""
        session = MagicMock()
        session.messages = _make_messages(30)

        engine = MagicMock()
        # 模拟压缩前后 token 数差异
        engine.estimate_tokens.side_effect = [1000, 400]
        engine.compress.return_value = [{"role": "system", "content": "sys"}]

        result = handle_compact(session, engine, None)
        assert "1000" in result
        assert "400" in result
        assert "600" in result  # 节省 600

    def test_calls_engine_compress(self):
        """调用 engine.compress"""
        session = MagicMock()
        session.messages = _make_messages(30)
        engine = MagicMock()
        engine.estimate_tokens.return_value = 500
        engine.compress.return_value = [{"role": "system", "content": "sys"}]

        handle_compact(session, engine, None)
        engine.compress.assert_called_once()

    def test_passes_custom_instruction_as_focus_topic(self):
        """自定义指令作为 focus_topic 传入"""
        session = MagicMock()
        session.messages = _make_messages(30)
        engine = MagicMock()
        engine.estimate_tokens.return_value = 500
        engine.compress.return_value = [{"role": "system", "content": "sys"}]

        handle_compact(session, engine, "focus on API design")
        _, kwargs = engine.compress.call_args
        assert kwargs.get("focus_topic") == "focus on API design"

    def test_no_custom_instruction_passes_none(self):
        """无自定义指令时 focus_topic=None"""
        session = MagicMock()
        session.messages = _make_messages(30)
        engine = MagicMock()
        engine.estimate_tokens.return_value = 500
        engine.compress.return_value = [{"role": "system", "content": "sys"}]

        handle_compact(session, engine, None)
        _, kwargs = engine.compress.call_args
        assert kwargs.get("focus_topic") is None

    def test_replaces_session_messages(self):
        """压缩后替换 session.messages"""
        session = MagicMock()
        session.messages = _make_messages(30)
        engine = MagicMock()
        engine.estimate_tokens.return_value = 500
        compressed = [{"role": "system", "content": "compressed"}]
        engine.compress.return_value = compressed

        handle_compact(session, engine, None)
        assert session.messages == compressed

    def test_empty_session_handled(self):
        """空 session 优雅处理"""
        session = MagicMock()
        session.messages = []
        engine = MagicMock()
        engine.estimate_tokens.return_value = 0
        engine.compress.return_value = []

        result = handle_compact(session, engine, None)
        assert "0" in result  # 显示 0 → 0

    def test_preserves_system_message_first(self):
        """压缩后 system 仍在 messages[0]"""
        session = MagicMock()
        session.messages = _make_messages(30)
        engine = MagicMock()
        engine.estimate_tokens.return_value = 500
        engine.compress.return_value = [
            {"role": "system", "content": "sys"},
            {"role": "user", "content": "compressed"},
        ]

        handle_compact(session, engine, None)
        assert session.messages[0]["role"] == "system"

    def test_engine_none_returns_fallback_message(self):
        """engine 为 None 时返回 fallback 消息"""
        session = MagicMock()
        session.messages = _make_messages(10)
        result = handle_compact(session, None, None)
        assert "不可用" in result or "unavailable" in result.lower() or "未初始化" in result

    def test_compress_exception_handled(self):
        """compress 抛异常时优雅降级"""
        session = MagicMock()
        session.messages = _make_messages(30)
        engine = MagicMock()
        engine.estimate_tokens.return_value = 500
        engine.compress.side_effect = RuntimeError("compress failed")

        result = handle_compact(session, engine, None)
        assert "失败" in result or "error" in result.lower() or "错误" in result
