# -*- coding: utf-8 -*-
"""CompactEffectivenessValidator 压缩效果验证器测试。

借鉴 zhikuncode-main 的 CompactEffectivenessValidator（46 行）：
- 70% 安全水位：压缩后 token <= effective_window * 0.70 才算有效
- effective_window = context_window - min(max_output_tokens, 20000)
- 窗口非正时回退到 max(context_window // 2, 10000)
"""
import pytest

from abyss.context_engine import (
    CompactEffectivenessValidator,
    SimpleTruncationEngine,
)


class TestCompactEffectivenessValidator:
    """CompactEffectivenessValidator 核心算法测试"""

    def test_create_default(self):
        v = CompactEffectivenessValidator()
        assert v.SAFETY_RATIO == 0.70

    def test_is_effective_below_safety_watermark(self):
        """压缩后 token 数低于 70% 安全水位时返回 True。"""
        v = CompactEffectivenessValidator()
        # context_window=128000, max_output=8000
        # effective_window = 128000 - 8000 = 120000
        # safety = 120000 * 0.70 = 84000
        # tokens_after=50000 < 84000 -> effective
        assert v.is_effective(
            tokens_after=50000, context_window=128000, max_output_tokens=8000
        ) is True

    def test_is_effective_above_safety_watermark(self):
        """压缩后 token 数高于 70% 安全水位时返回 False。"""
        v = CompactEffectivenessValidator()
        # safety = 84000, tokens_after=90000 > 84000 -> not effective
        assert v.is_effective(
            tokens_after=90000, context_window=128000, max_output_tokens=8000
        ) is False

    def test_is_effective_at_exact_watermark(self):
        """恰好等于安全水位时返回 True（<=，含等号）。"""
        v = CompactEffectivenessValidator()
        # safety = 84000.0, tokens_after=84000 == safety -> effective
        assert v.is_effective(
            tokens_after=84000, context_window=128000, max_output_tokens=8000
        ) is True

    def test_effective_window_caps_max_output_at_20000(self):
        """max_output_tokens 超过 20000 时按 20000 计。"""
        v = CompactEffectivenessValidator()
        # context_window=128000, max_output=50000 -> cap to 20000
        # effective_window = 128000 - 20000 = 108000
        # safety = 108000 * 0.70 = 75600
        # tokens_after=75000 < 75600 -> effective
        assert v.is_effective(
            tokens_after=75000, context_window=128000, max_output_tokens=50000
        ) is True
        # tokens_after=76000 > 75600 -> not effective
        assert v.is_effective(
            tokens_after=76000, context_window=128000, max_output_tokens=50000
        ) is False

    def test_effective_window_fallback_when_negative(self):
        """当 context_window < max_output_tokens 时，effective_window 回退到 max(ctx/2, 10000)。"""
        v = CompactEffectivenessValidator()
        # context_window=10000, max_output=20000 -> effective_window would be -10000
        # fallback: max(10000 // 2, 10000) = 10000
        # safety = 10000 * 0.70 = 7000
        # tokens_after=6000 < 7000 -> effective
        assert v.is_effective(
            tokens_after=6000, context_window=10000, max_output_tokens=20000
        ) is True
        # tokens_after=7500 > 7000 -> not effective
        assert v.is_effective(
            tokens_after=7500, context_window=10000, max_output_tokens=20000
        ) is False

    def test_effective_window_fallback_minimum_10000(self):
        """回退值至少 10000，即使 context_window 很小。"""
        v = CompactEffectivenessValidator()
        # context_window=5000, max_output=20000 -> effective_window would be -15000
        # fallback: max(5000 // 2, 10000) = 10000
        # safety = 10000 * 0.70 = 7000
        assert v.is_effective(
            tokens_after=6000, context_window=5000, max_output_tokens=20000
        ) is True

    def test_tokens_to_release_zero_when_effective(self):
        """压缩已有效时返回 0。"""
        v = CompactEffectivenessValidator()
        # safety = 84000, tokens_after=50000 -> 0 to release
        result = v.tokens_to_release(
            tokens_after=50000, context_window=128000, max_output_tokens=8000
        )
        assert result == 0

    def test_tokens_to_release_positive_when_not_effective(self):
        """压缩无效时返回需释放的正数 token 数。"""
        v = CompactEffectivenessValidator()
        # safety = 84000, tokens_after=90000 -> need release 6000
        result = v.tokens_to_release(
            tokens_after=90000, context_window=128000, max_output_tokens=8000
        )
        assert result == 6000

    def test_tokens_to_release_large_overflow(self):
        """tokens_after 远超安全水位时返回较大值。"""
        v = CompactEffectivenessValidator()
        # safety = 84000, tokens_after=200000 -> need release 116000
        result = v.tokens_to_release(
            tokens_after=200000, context_window=128000, max_output_tokens=8000
        )
        assert result == 116000

    def test_tokens_to_release_at_exact_watermark(self):
        """恰好等于安全水位时返回 0（无需释放）。"""
        v = CompactEffectivenessValidator()
        result = v.tokens_to_release(
            tokens_after=84000, context_window=128000, max_output_tokens=8000
        )
        assert result == 0


class TestSimpleTruncationEngineValidationIntegration:
    """SimpleTruncationEngine 集成 CompactEffectivenessValidator 测试"""

    def test_engine_has_validator(self):
        """SimpleTruncationEngine 实例应持有 CompactEffectivenessValidator。"""
        engine = SimpleTruncationEngine()
        assert isinstance(engine.validator, CompactEffectivenessValidator)

    def test_compress_with_validation_default_off(self):
        """validate_effectiveness=False 时压缩行为与原版一致。"""
        engine = SimpleTruncationEngine(
            context_length=128000, protect_first_n=1, protect_last_n=2
        )
        messages = [
            {"role": "system", "content": "sys"},
        ]
        # 12 条非 system 消息
        for i in range(12):
            messages.append({"role": "user", "content": f"msg {i}"})
            messages.append({"role": "assistant", "content": f"reply {i}"})
        # 不验证时返回原结果
        result_default = engine.compress(
            messages,
            current_tokens=100000,
            max_output_tokens=8000,
            validate_effectiveness=False,
        )
        assert len(result_default) < len(messages)

    def test_compress_with_validation_escalates_when_not_effective(self):
        """压缩后仍超安全水位时自动升级到 aggressive 模式。"""
        engine = SimpleTruncationEngine(
            context_length=128000, protect_first_n=1, protect_last_n=8
        )
        # 构造大量消息使初始压缩后仍超 70% 水位
        messages = [{"role": "system", "content": "sys"}]
        for i in range(50):
            messages.append({"role": "user", "content": f"msg {i} " * 200})
            messages.append({"role": "assistant", "content": f"reply {i} " * 200})

        # 启用验证 + 提供 max_output_tokens
        result = engine.compress(
            messages,
            current_tokens=120000,  # 接近上限
            max_output_tokens=8000,
            validate_effectiveness=True,
        )
        # aggressive 模式保护条数减半，结果应比默认更短
        assert len(result) < len(messages)
        # 应包含截断标记
        assert any("[上下文压缩]" in m.get("content", "") for m in result)

    def test_compress_with_validation_falls_back_to_emergency(self):
        """aggressive 仍无效时降级到 emergency 模式。

        构造场景：小 context_window + 超长消息，使 aggressive 压缩后仍超安全水位。
        - context_window=10000, max_output=1000
        - safety = (10000-1000) * 0.70 = 6300 tokens
        - 4 条 20000 字符的 tail 消息（aggressive 保护 4 条）≈ 8000+ tokens > 6300
        """
        engine = SimpleTruncationEngine(
            context_length=10000, protect_first_n=1, protect_last_n=8
        )
        # 用真实英文单词而非重复字符，避免 BPE 合并导致 token 数被低估
        long_content = "the quick brown fox jumps over lazy dog " * 500  # ~22500 chars
        messages = [{"role": "system", "content": "sys"}]
        for i in range(20):
            messages.append({"role": "user", "content": long_content})

        result = engine.compress(
            messages,
            current_tokens=9000,
            max_output_tokens=1000,
            validate_effectiveness=True,
        )
        # emergency 模式仅保留 system + 最近 1 轮 + 截断标记
        assert len(result) <= 5
        assert any("[紧急压缩]" in m.get("content", "") for m in result)
        assert engine.last_escalation_level == "emergency"

    def test_compress_validation_skipped_when_max_output_none(self):
        """未提供 max_output_tokens 时跳过验证，行为同默认。"""
        engine = SimpleTruncationEngine(protect_first_n=1, protect_last_n=2)
        messages = [{"role": "system", "content": "sys"}]
        for i in range(20):
            messages.append({"role": "user", "content": f"msg {i}"})

        result = engine.compress(
            messages,
            current_tokens=100000,
            max_output_tokens=None,
            validate_effectiveness=True,
        )
        # 跳过验证，应只做一轮普通截断
        assert len(result) < len(messages)
        assert any("[上下文压缩]" in m.get("content", "") for m in result)

    def test_compress_validation_no_escalation_when_already_effective(self):
        """普通压缩已有效时不升级，保留更多上下文。"""
        engine = SimpleTruncationEngine(
            context_length=128000, protect_first_n=1, protect_last_n=2
        )
        # 少量消息，压缩后 token 数远低于水位
        messages = [{"role": "system", "content": "sys"}]
        for i in range(10):
            messages.append({"role": "user", "content": f"short msg {i}"})

        result = engine.compress(
            messages,
            current_tokens=5000,  # 很低
            max_output_tokens=8000,
            validate_effectiveness=True,
        )
        # 普通压缩已有效，不应升级
        # 但消息数可能太少不触发压缩，result 等于 messages
        assert result[0] == messages[0]  # system 保留

    def test_compress_validation_records_escalation_level(self):
        """验证后记录升级级别供日志使用。"""
        engine = SimpleTruncationEngine(protect_first_n=1, protect_last_n=8)
        messages = [{"role": "system", "content": "sys"}]
        for i in range(100):
            messages.append({"role": "user", "content": "x" * 1000})

        engine.compress(
            messages,
            current_tokens=150000,
            max_output_tokens=8000,
            validate_effectiveness=True,
        )
        # 升级级别应被记录到 engine.last_escalation_level
        assert engine.last_escalation_level in ("aggressive", "emergency", "none")


class TestTokensToReleaseHelper:
    """tokens_to_release 辅助方法测试"""

    def test_returns_zero_for_effective_compression(self):
        v = CompactEffectivenessValidator()
        result = v.tokens_to_release(50000, 128000, 8000)
        assert result == 0

    def test_returns_correct_amount(self):
        v = CompactEffectivenessValidator()
        # safety = 84000, tokens_after=100000 -> release 16000
        result = v.tokens_to_release(100000, 128000, 8000)
        assert result == 16000

    def test_never_returns_negative(self):
        """即使 tokens_after 远低于水位也返回 0 而非负数。"""
        v = CompactEffectivenessValidator()
        result = v.tokens_to_release(100, 128000, 8000)
        assert result == 0
        assert result >= 0
