# -*- coding: utf-8 -*-
"""SelfCorrectionLoop 自纠错循环测试。

借鉴 zhikuncode-main 的 SelfCorrectionLoop：工具执行失败后生成
结构化再提示供 LLM 修正参数，最多重试 max_attempts 次，提升 bug 修复成功率。

测试只验证 SelfCorrectionLoop 类本身逻辑，不依赖真实 LLM 调用。
"""
import pytest

from abyss.agent_loop import SelfCorrectionLoop


def make_tool_call(name: str, args: str = "{}") -> dict:
    """构造 OpenAI 格式 tool_call mock。"""
    return {
        "id": f"call_{name}",
        "type": "function",
        "function": {"name": name, "arguments": args},
    }


def make_failure(error: str = "未找到匹配的旧内容") -> dict:
    """构造失败结果。"""
    return {"success": False, "error": error}


def make_success() -> dict:
    """构造成功结果。"""
    return {"success": True, "output": "ok"}


class TestSelfCorrectionLoopBasic:
    """SelfCorrectionLoop 基本行为。"""

    def test_initial_attempts_zero(self):
        """初始化时 attempts 为 0。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        assert loop.attempts == 0

    def test_should_retry_on_failure(self):
        """工具失败时 should_retry 返回 True。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure()
        assert loop.should_retry(tc, result) is True

    def test_should_not_retry_on_success(self):
        """工具成功时 should_retry 返回 False。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_success()
        assert loop.should_retry(tc, result) is False


class TestBuildCorrectionPrompt:
    """build_correction_prompt 生成结构化提示。"""

    def test_contains_tool_name_and_error(self):
        """提示包含工具名和失败原因。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure("未找到匹配的旧内容")
        prompt = loop.build_correction_prompt(tc, result, attempt=1)
        assert "file_edit" in prompt
        assert "未找到匹配的旧内容" in prompt

    def test_file_edit_suggestion(self):
        """file_edit 失败建议先 file_read 确认内容。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure()
        prompt = loop.build_correction_prompt(tc, result, attempt=1)
        assert "file_read" in prompt

    def test_shell_exec_suggestion(self):
        """shell_exec 失败建议检查命令语法。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("shell_exec")
        result = make_failure()
        prompt = loop.build_correction_prompt(tc, result, attempt=1)
        assert "检查命令语法" in prompt

    def test_generic_suggestion(self):
        """通用工具失败包含根据错误信息调整参数。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("unknown_tool")
        result = make_failure()
        prompt = loop.build_correction_prompt(tc, result, attempt=1)
        assert "根据错误信息调整参数" in prompt


class TestFailureTriggersRetry:
    """失败触发重试。"""

    def test_failure_increments_attempts(self):
        """失败一次后 attempts 计为 1。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure()
        loop.should_retry(tc, result)
        assert loop.attempts == 1

    def test_should_retry_below_max(self):
        """attempts < max_attempts 时返回 True。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure()
        # 第一次失败
        assert loop.should_retry(tc, result) is True
        assert loop.attempts == 1
        # 第二次失败
        assert loop.should_retry(tc, result) is True
        assert loop.attempts == 2

    def test_correction_prompt_after_failure(self):
        """失败后能生成下一轮提示。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure()
        loop.should_retry(tc, result)
        prompt = loop.build_correction_prompt(tc, result, attempt=loop.attempts)
        assert "file_edit" in prompt


class TestSuccessStopsLoop:
    """成功后立即停止。"""

    def test_success_does_not_retry(self):
        """第 1 次重试成功后不再重试。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        fail = make_failure()
        success = make_success()
        # 失败一次
        assert loop.should_retry(tc, fail) is True
        # 成功
        assert loop.should_retry(tc, success) is False
        # attempts 不再增长
        assert loop.attempts == 1


class TestExhaustedLoop:
    """达到上限放弃。"""

    def test_max_attempts_exhausted(self):
        """连续失败 3 次后 should_retry 返回 False。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        result = make_failure()
        # 3 次失败均允许重试
        assert loop.should_retry(tc, result) is True  # attempt 1
        assert loop.should_retry(tc, result) is True  # attempt 2
        assert loop.should_retry(tc, result) is True  # attempt 3
        assert loop.attempts == 3
        # 第 4 次不应再重试
        assert loop.should_retry(tc, result) is False

    def test_exhausted_message(self):
        """build_exhausted_message 返回耗尽提示。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        msg = loop.build_exhausted_message()
        assert "自纠错循环已耗尽 3 次重试" in msg


class TestOnlyFailureTriggers:
    """仅对工具失败触发重试。"""

    def test_success_result_no_retry(self):
        """成功结果不触发重试。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        # 各种成功形式
        assert loop.should_retry(tc, {"success": True, "output": "ok"}) is False
        assert loop.should_retry(tc, {"success": True}) is False
        # 无 success 字段默认视为成功
        assert loop.should_retry(tc, {"output": "ok"}) is False

    def test_failure_result_triggers_retry(self):
        """失败结果触发重试。"""
        loop = SelfCorrectionLoop(max_attempts=3)
        tc = make_tool_call("file_edit")
        assert loop.should_retry(tc, {"success": False, "error": "失败"}) is True
        assert loop.should_retry(tc, {"success": False, "error": "another"}) is True
