# -*- coding: utf-8 -*-
"""SnipService 头尾保留截断测试（Phase 7 Tier 1-1 BE）

借鉴 zhikuncode SnipService：
- 头尾保留，中间插入截断标记
- 标记含字符数与行数统计
- 可自定义 head_ratio
- 与 tools/registry.py format_tool_result 集成修复"丢 returncode"痛点
"""
import pytest

from abyss.context.snip import snip


class TestSnipBasic:
    """基本功能测试"""

    def test_short_content_unchanged(self):
        """短内容不截断，原样返回"""
        content = "hello world"
        result = snip(content, budget=100)
        assert result == "hello world"

    def test_exact_budget_unchanged(self):
        """内容长度等于 budget 时不截断"""
        content = "a" * 100
        result = snip(content, budget=100)
        assert result == "a" * 100

    def test_long_content_truncated(self):
        """长内容触发截断，结果短于 budget"""
        content = "a" * 1000
        result = snip(content, budget=100)
        assert len(result) <= 100
        assert "a" in result  # 仍含部分内容

    def test_returns_string_type(self):
        """返回值类型为 str"""
        result = snip("test", budget=100)
        assert isinstance(result, str)


class TestSnipHeadTailPreservation:
    """头尾保留逻辑测试"""

    def test_head_preserved(self):
        """头部内容保留"""
        content = "HEAD" + "x" * 200 + "TAIL"
        result = snip(content, budget=50)
        assert result.startswith("HEAD")

    def test_tail_preserved(self):
        """尾部内容保留（修复 returncode 丢失痛点）"""
        content = "HEAD" + "x" * 200 + "TAIL"
        result = snip(content, budget=50)
        assert result.endswith("TAIL")

    def test_middle_marker_present(self):
        """中间截断标记存在"""
        content = "head" + "x" * 200 + "tail"
        result = snip(content, budget=80)
        assert "截断" in result

    def test_middle_marker_contains_char_count(self):
        """截断标记含字符数"""
        content = "a" * 500
        result = snip(content, budget=80)
        assert "500" in result  # 原始字符数

    def test_middle_marker_contains_line_count(self):
        """截断标记含行数统计"""
        content = "\n".join(["line"] * 100)  # 100 行
        result = snip(content, budget=80)
        assert "100" in result  # 行数


class TestSnipHeadRatio:
    """head_ratio 自定义测试"""

    def test_default_head_ratio_half(self):
        """默认 head_ratio=0.5，头尾各占一半"""
        content = "H" * 50 + "MIDDLE" + "T" * 50
        result = snip(content, budget=40)
        # 头部 H 应保留，尾部 T 应保留
        assert "H" in result
        assert "T" in result

    def test_custom_head_ratio_70(self):
        """head_ratio=0.7，头部占 70%"""
        content = "H" * 50 + "MIDDLE" + "T" * 50
        result = snip(content, budget=40, head_ratio=0.7)
        assert "H" in result
        assert "T" in result
        # 头部预算更多
        head_count = result.count("H")
        tail_count = result.count("T")
        assert head_count >= tail_count

    def test_custom_head_ratio_30(self):
        """head_ratio=0.3，尾部占 70%"""
        content = "H" * 50 + "MIDDLE" + "T" * 50
        result = snip(content, budget=40, head_ratio=0.3)
        assert "H" in result
        assert "T" in result
        head_count = result.count("H")
        tail_count = result.count("T")
        assert tail_count >= head_count

    def test_head_ratio_zero_only_tail(self):
        """head_ratio=0.0，仅保留尾部"""
        content = "HEAD" + "x" * 200 + "TAIL"
        result = snip(content, budget=30, head_ratio=0.0)
        assert result.endswith("TAIL")
        assert "HEAD" not in result

    def test_head_ratio_one_only_head(self):
        """head_ratio=1.0，仅保留头部"""
        content = "HEAD" + "x" * 200 + "TAIL"
        result = snip(content, budget=30, head_ratio=1.0)
        assert result.startswith("HEAD")
        # 尾部可能完全丢失
        # 但标记仍在
        assert "截断" in result


class TestSnipEdgeCases:
    """边界情况测试"""

    def test_empty_content(self):
        """空内容返回空"""
        result = snip("", budget=100)
        assert result == ""

    def test_single_char_content(self):
        """单字符内容不截断"""
        result = snip("a", budget=100)
        assert result == "a"

    def test_budget_zero_returns_empty_or_marker(self):
        """budget=0 时返回空或仅标记"""
        result = snip("long content here", budget=0)
        # budget=0 时无法容纳任何内容，返回空字符串或最小标记
        assert isinstance(result, str)

    def test_budget_smaller_than_marker(self):
        """budget 小于标记长度时优雅降级"""
        content = "a" * 1000
        # budget=10 太小，连标记都放不下
        result = snip(content, budget=10)
        assert isinstance(result, str)
        assert len(result) <= 10

    def test_budget_negative_returns_empty(self):
        """budget 为负数时返回空"""
        result = snip("content", budget=-10)
        assert result == "" or isinstance(result, str)


class TestSnipUnicode:
    """Unicode 内容测试"""

    def test_chinese_content_truncated(self):
        """中文内容截断保留头尾"""
        content = "这是头部" + "中" * 200 + "这是尾部"
        result = snip(content, budget=60)
        assert "这是头部" in result
        assert "这是尾部" in result

    def test_mixed_content(self):
        """中英混合内容"""
        content = "Start 开始" + "x" * 200 + "End 结束"
        result = snip(content, budget=50)
        assert "Start" in result or "开始" in result
        assert "End" in result or "结束" in result

    def test_emoji_content(self):
        """含 emoji 内容"""
        content = "🚀 start" + "x" * 100 + "🏁 end"
        result = snip(content, budget=40)
        assert isinstance(result, str)


class TestSnipRealisticScenarios:
    """真实场景测试"""

    def test_build_log_preserves_error_at_end(self):
        """构建日志场景：错误信息常在末尾"""
        # 模拟 npm install 输出：大量进度信息 + 末尾错误
        content_lines = ["npm install progress..."] * 50
        content_lines.append("ERROR: Cannot find module 'missing-pkg'")
        content_lines.append("Exit code 1")
        content = "\n".join(content_lines)

        result = snip(content, budget=200)
        # 关键：末尾错误信息必须保留
        assert "ERROR" in result
        assert "Exit code 1" in result

    def test_shell_exec_stderr_preserved(self):
        """shell_exec stderr 末尾 returncode 保留"""
        stderr_lines = ["warning: deprecated"] * 30
        stderr_lines.append("returncode: 1")
        content = "\n".join(stderr_lines)

        result = snip(content, budget=100)
        assert "returncode: 1" in result

    def test_python_traceback_preserved(self):
        """Python traceback 末尾异常类型保留"""
        content = (
            "Traceback (most recent call last):\n"
            + "  File ...\n" * 20
            + "ValueError: invalid literal for int() with base 10: 'abc'"
        )
        result = snip(content, budget=150)
        assert "ValueError" in result
        assert "Traceback" in result

    def test_single_line_long_content(self):
        """单行超长内容截断"""
        content = "a" * 1000  # 单行
        result = snip(content, budget=100)
        # 单行内容：行数为 1
        assert "1" in result  # 行数标记
        assert len(result) <= 100


class TestSnipIntegration:
    """集成测试：与 format_tool_result 配合"""

    def test_format_tool_result_shell_exec_long_stdout(self):
        """shell_exec 长输出截断后保留尾部 returncode"""
        from abyss.tools.registry import format_tool_result

        # 模拟 shell_exec 输出：长 stdout + 非零 returncode
        stdout = "line\n" * 500  # 2500 字符
        result = {
            "success": False,
            "stdout": stdout,
            "stderr": "ERROR: something went wrong",
            "returncode": 1,
        }

        formatted = format_tool_result("shell_exec", result, max_len=200)
        # 关键：returncode 必须保留（在 dict 顶层）
        assert "returncode" in formatted
        assert "1" in formatted
        # stderr 完整保留（短）
        assert "ERROR: something went wrong" in formatted

    def test_format_tool_result_shell_exec_long_stderr(self):
        """shell_exec 长 stderr 截断后保留尾部错误"""
        from abyss.tools.registry import format_tool_result

        stderr = "warning\n" * 300 + "FATAL ERROR: disk full"
        result = {
            "success": False,
            "stdout": "",
            "stderr": stderr,
            "returncode": 2,
        }

        formatted = format_tool_result("shell_exec", result, max_len=200)
        assert "FATAL ERROR: disk full" in formatted
        assert "returncode" in formatted

    def test_format_tool_result_shell_exec_short_unchanged(self):
        """shell_exec 短输出不截断"""
        from abyss.tools.registry import format_tool_result

        result = {
            "success": True,
            "stdout": "ok",
            "stderr": "",
            "returncode": 0,
        }
        formatted = format_tool_result("shell_exec", result, max_len=4000)
        # 短内容原样保留
        assert "ok" in formatted
        assert "截断" not in formatted
