"""
测试 react_loop.py 中的 Action Input 解析增强
1. 多行 JSON heredoc 收集
2. ActionInput 无空格容错
3. json.dumps 安全转义兜底
"""
import json
import re
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))


class DummyLoop:
    def _fix_json_format(self, s):
        s = re.sub(r",\s*}", "}", s)
        s = re.sub(r",\s*]", "]", s)
        s = re.sub(r'\\n(\s*[}\]",:\]])', r"\1", s)
        return s


def simulate_parse(response_text: str):
    """模拟 react_loop 的文本解析逻辑（含三层兜底）"""
    dummy = DummyLoop()
    lines = response_text.split("\n")
    actions_parsed = []

    i = 0
    while i < len(lines):
        line = lines[i].strip()
        action_match = re.match(r"Action\s*(\d+)?[:\s]+(?!Input)(.+)", line, re.IGNORECASE)
        if action_match:
            action_name = action_match.group(2).strip().strip('`"\' ')
            action_input = {}
            found_input = False

            for j in range(i + 1, min(i + 11, len(lines))):
                input_line = lines[j].strip()
                if re.match(r"Action\s*(\d+)?[:\s]+(?!Input)", input_line, re.IGNORECASE):
                    break
                input_match = re.match(r"Action\s*Input\s*(\d+)?[:\s]+(.+)", input_line, re.IGNORECASE)
                if input_match:
                    input_text = input_match.group(2).strip()

                    if input_text.startswith("{"):
                        try:
                            clean = input_text.replace("```json", "").replace("```", "").strip()
                            clean = dummy._fix_json_format(clean)
                            action_input = json.loads(clean)
                            found_input = True
                            break
                        except json.JSONDecodeError:
                            # 多行收集
                            collected_lines = [input_text]
                            for k in range(j + 1, min(j + 20, len(lines))):
                                if re.match(r"\s*(?:Thought|Action|Observation)[:\s]", lines[k], re.IGNORECASE):
                                    break
                                if not lines[k].strip():
                                    break
                                collected_lines.append(lines[k])

                                # 方式1: 字面换行
                                j1 = "\n".join(collected_lines)
                                c1 = j1.replace("```json", "").replace("```", "").strip()
                                c1 = dummy._fix_json_format(c1)
                                try:
                                    action_input = json.loads(c1)
                                    found_input = True
                                    break
                                except json.JSONDecodeError:
                                    pass

                            if found_input:
                                break

                            # 方式2: json.dumps 安全转义
                            full_raw = "\n".join(collected_lines)
                            cmd_match = re.match(
                                r'\s*\{\s*"(command|cmd|shell|script|exec)"\s*:\s*"',
                                full_raw,
                            )
                            if cmd_match:
                                key_name = cmd_match.group(1)
                                raw_value = full_raw[cmd_match.end():]
                                raw_value = re.sub(r'"\s*\}?\s*$', '', raw_value)
                                safe_value = json.dumps(raw_value)
                                for kn in [key_name, "command"]:
                                    safe_json = '{"' + kn + '": ' + safe_value + '}'
                                    try:
                                        action_input = json.loads(safe_json)
                                        found_input = True
                                        break
                                    except json.JSONDecodeError:
                                        continue
                            if found_input:
                                break

                    if found_input:
                        break
                    action_input = {}
                    found_input = True
                    break

            actions_parsed.append((action_name, action_input))
        i += 1

    return actions_parsed


class TestMultilineJson:
    """多行 JSON 解析"""

    def test_single_line_json(self):
        resp = 'Thought: test\nAction: run_shell\nAction Input: {"command": "echo hello"}\n'
        actions = simulate_parse(resp)
        assert len(actions) == 1
        assert actions[0][0] == "run_shell"
        assert actions[0][1]["command"] == "echo hello"

    def test_heredoc_multiline(self):
        resp = """Thought: test
Action: run_shell
Action Input: {"command": "cat > f << 'EOF'
print('hello')
EOF"}
"""
        actions = simulate_parse(resp)
        assert len(actions) == 1
        cmd = actions[0][1]["command"]
        assert "cat > f << 'EOF'" in cmd
        assert "print('hello')" in cmd
        assert "EOF" in cmd

    def test_content_with_double_quotes(self):
        """内容含双引号——json.dumps 兜底"""
        resp = '''Thought: test
Action: run_shell
Action Input: {"command": "echo \\"hello world\\" > f.txt"}
'''
        actions = simulate_parse(resp)
        assert len(actions) == 1
        cmd = actions[0][1]["command"]
        assert "hello" in cmd

    def test_content_in_heredoc_with_quotes(self):
        """heredoc 内容含双引号"""
        resp = '''Thought: test
Action: run_shell
Action Input: {"command": "cat > f.md << 'EOF'
# Title
这是 "引号" 内容
more text
EOF"}
'''
        actions = simulate_parse(resp)
        assert len(actions) == 1
        cmd = actions[0][1]["command"]
        assert '"引号"' in cmd
        assert "# Title" in cmd

    def test_actioninput_no_space(self):
        """ActionInput 无空格"""
        resp = 'Thought: test\nAction: run_shell\nActionInput: {"command": "ls"}\n'
        actions = simulate_parse(resp)
        assert len(actions) == 1
        assert actions[0][0] == "run_shell"
        assert actions[0][1]["command"] == "ls"

    def test_actioninput_no_space_multiline(self):
        """ActionInput 无空格 + heredoc"""
        resp = """Thought: test
Action: run_shell
ActionInput: {"command": "cat > f << 'EOF'
line1
line2
EOF"}
"""
        actions = simulate_parse(resp)
        assert len(actions) == 1
        cmd = actions[0][1]["command"]
        assert "line1" in cmd
        assert "line2" in cmd

    def test_incomplete_json_fallback(self):
        """不完整 JSON（缺闭合 }）——json.dumps 兜底"""
        resp = '''Thought: test
Action: run_shell
Action Input: {"command": "echo hello'''
        actions = simulate_parse(resp)
        assert len(actions) == 1
        assert actions[0][1]["command"] == "echo hello"

    def test_multiple_actions(self):
        resp = """Thought: test
Action 1: run_shell
Action Input 1: {"command": "ls"}
Action 2: run_shell
Action Input 2: {"command": "pwd"}
"""
        actions = simulate_parse(resp)
        assert len(actions) == 2
        assert actions[0][1]["command"] == "ls"
        assert actions[1][1]["command"] == "pwd"

    def test_backslash_in_command_preserved(self):
        """反斜杠不应被错误解析"""
        resp = r'''Thought: test
Action: run_shell
Action Input: {"command": "cat > f.sh << 'EOF'
#!/bin/bash
echo hello
EOF"}
'''
        actions = simulate_parse(resp)
        assert len(actions) == 1
        cmd = actions[0][1]["command"]
        assert "#!/bin/bash" in cmd or "bash" in cmd
        # \b 不应变成退格符
        assert "\x08" not in cmd
