"""
测试 SafetyGuard 放宽后的行为：
- 管道拆分 (| && || ;)
- ".." 精准匹配（不带 .. 的代码串不应误判为路径）
- restrict_to_project=False 时允许项目外路径
- relax sudo/rm/chmod 的项目外限制
"""

import sys
import tempfile
from pathlib import Path

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

import pytest
from aacode.utils.safety import SafetyGuard


@pytest.fixture
def guard_relaxed():
    """默认宽松模式：restrict_to_project=False, dangerous_command_action=log, non-interactive"""
    with tempfile.TemporaryDirectory() as td:
        yield SafetyGuard(Path(td), interactive=False, dangerous_command_action="log", restrict_to_project=False)


@pytest.fixture
def guard_strict():
    """严格模式：restrict_to_project=True, dangerous_command_action=reject, non-interactive"""
    with tempfile.TemporaryDirectory() as td:
        yield SafetyGuard(Path(td), interactive=False, dangerous_command_action="reject", restrict_to_project=True)


# ═══════════════════════════════════════════════════════════════
# 管道拆分测试
# ═══════════════════════════════════════════════════════════════

class TestPipelineSplitting:
    """管道/分隔符应被拆分，子命令独立检查"""

    def test_pipe_basic(self, guard_relaxed):
        """curl | python3 -c '...' 应通过（用户的原始场景）"""
        cmd = (
            'curl -sL "https://example.com" | '
            "python3 -c \"print('No chapters found, searching...')\""
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"pipe should be split, got: {r}"

    def test_pipe_with_python_code_containing_dots(self, guard_relaxed):
        """python -c 代码中包含 ... 不应误判为路径"""
        cmd = "python3 -c \"import sys; print('...loading...')\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"code with ... should not trigger path check: {r}"

    def test_pipe_with_python_code_containing_two_dots(self, guard_relaxed):
        """代码中含 .. 作为文件名片段不误判，但作为路径组件应正确识别"""
        cmd = "python3 -c \"print('file..txt')\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"'file..txt' in code should not be a path: {r}"

    def test_double_ampersand(self, guard_relaxed):
        """cmd1 && cmd2 应拆分为两条命令"""
        cmd = "echo hello && echo world"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"&& should be split: {r}"

    def test_double_pipe(self, guard_relaxed):
        """cmd1 || cmd2 应拆分"""
        cmd = "false || echo fallback"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"|| should be split: {r}"

    def test_semicolon(self, guard_relaxed):
        """cmd1; cmd2 应拆分"""
        cmd = "echo a; echo b"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"; should be split: {r}"

    def test_pipe_triple(self, guard_relaxed):
        """三个管道的命令"""
        cmd = "cat file.txt | grep foo | sort | uniq"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"triple pipe should work: {r}"

    def test_pipe_with_redirection(self, guard_relaxed):
        """管道 + 重定向"""
        cmd = "curl example.com 2>&1 | python3 -c \"print('ok')\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"pipe with 2>&1 should work: {r}"

    def test_pipe_rejects_dangerous_in_second_cmd(self, guard_strict):
        """管道中第二个命令是危险的，整体应拒绝"""
        cmd = "echo hello | rm -rf /"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"dangerous cmd in pipe should be rejected: {r}"

    def test_no_pipe_just_normal_cmd(self, guard_relaxed):
        """普通命令不受管道拆分影响"""
        cmd = "ls -la"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"normal cmd should work: {r}"


# ═══════════════════════════════════════════════════════════════
# ".." 精准路径匹配
# ═══════════════════════════════════════════════════════════════

class TestDotDotPathDetection:
    """.. 只应匹配作为路径组件的场景"""

    def test_ellipsis_not_path(self, guard_relaxed):
        """print('...') 中的 ... 不是路径组件"""
        cmd = "python3 -c \"print('loading...')\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"ellipsis should not trigger path check: {r}"

    def test_two_dots_in_filename_not_path(self, guard_relaxed):
        """file..txt 中的 .. 不是路径组件（没有 / 分隔）"""
        cmd = "python3 -c \"x = 'backup..sql'\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f".. in filename without / should not trigger: {r}"

    def test_dotdot_as_path_component_detected(self, guard_relaxed):
        """../somedir 中的 .. 是真实的路径遍历，应被检测"""
        cmd = "ls ../somedir"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"real .. path should be allowed in relaxed mode: {r}"

    def test_dotdot_path_component_mid_path(self, guard_relaxed):
        """path/../other 应被检测为路径遍历"""
        cmd = "cat foo/../bar"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"mid-path .. should be allowed in relaxed mode: {r}"

    def test_standalone_dotdot(self, guard_relaxed):
        """单独的 .. 参数（如 cd ..）"""
        cmd = "echo .."
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"standalone .. should work: {r}"


# ═══════════════════════════════════════════════════════════════
# restrict_to_project 测试
# ═══════════════════════════════════════════════════════════════

class TestRestrictToProject:
    """restrict_to_project 对路径检查的影响"""

    def test_relaxed_allows_system_path(self, guard_relaxed):
        """restrict_to_project=False 时，系统路径应允许"""
        cmd = "ls /usr/local/bin"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed mode should allow system paths: {r}"

    def test_relaxed_allows_etc_path(self, guard_relaxed):
        """restrict_to_project=False 时，/etc 路径应允许"""
        cmd = "cat /etc/hosts"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed should allow /etc: {r}"

    def test_relaxed_allows_home_path(self, guard_relaxed):
        """restrict_to_project=False 时，home 目录应允许"""
        cmd = "ls ~"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed should allow home: {r}"

    def test_strict_blocks_outside_path(self, guard_strict):
        """restrict_to_project=True 时，项目外且不在系统白名单中的路径应被拒绝"""
        cmd = "touch /srv/testfile"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"strict should block outside paths: {r}"

    def test_strict_allows_project_path(self, guard_strict):
        """restrict_to_project=True 时，项目内路径应允许"""
        project_root = guard_strict.project_root
        cmd = f"touch {project_root}/test.txt"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"strict should allow project paths: {r}"


# ═══════════════════════════════════════════════════════════════
# sudo 放宽测试
# ═══════════════════════════════════════════════════════════════

class TestSudoRelaxation:
    """sudo 白名单在 relax 模式下应放开"""

    def test_sudo_relaxed_allows_any_subcmd(self, guard_relaxed):
        """restrict_to_project=False 时，sudo 任意子命令应允许"""
        cmd = "sudo whoami"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed sudo should allow any subcommand: {r}"

    def test_sudo_relaxed_allows_make(self, guard_relaxed):
        """restrict_to_project=False 时，sudo make 应允许"""
        cmd = "sudo make install"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed should allow sudo make: {r}"

    def test_sudo_strict_blocks_non_whitelist(self, guard_strict):
        """restrict_to_project=True 时，sudo 非白名单子命令应拒绝"""
        cmd = "sudo whoami"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"strict should block sudo whoami: {r}"

    def test_sudo_strict_allows_whitelisted(self, guard_strict):
        """restrict_to_project=True 时，sudo pip 应允许"""
        cmd = "sudo pip install requests"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"strict should allow sudo pip: {r}"


# ═══════════════════════════════════════════════════════════════
# rm 放宽测试
# ═══════════════════════════════════════════════════════════════

class TestRmRelaxation:
    """rm 的项目外限制在 relax 模式下应放开"""

    def test_rm_rf_outside_project_relaxed(self, guard_relaxed):
        """restrict_to_project=False 时，rm -rf /tmp/xxx 应允许"""
        cmd = "rm -rf /tmp/some_test_dir"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed should allow rm outside project: {r}"

    def test_rm_rf_root_always_blocked(self, guard_relaxed):
        """即使 restrict_to_project=False，rm -rf / 仍应拒绝"""
        cmd = "rm -rf /"
        r = guard_relaxed.check_command(cmd)
        assert not r["allowed"], f"rm -rf / should always be blocked: {r}"

    def test_rm_rf_etc_always_blocked(self, guard_relaxed):
        """即使 restrict_to_project=False，rm -rf /etc 仍应拒绝"""
        cmd = "rm -rf /etc"
        r = guard_relaxed.check_command(cmd)
        assert not r["allowed"], f"rm -rf /etc should always be blocked: {r}"

    def test_rm_rf_usr_always_blocked(self, guard_relaxed):
        """rm -rf /usr 始终拒绝"""
        cmd = "rm -rf /usr"
        r = guard_relaxed.check_command(cmd)
        assert not r["allowed"], f"rm -rf /usr should be blocked: {r}"

    def test_rm_rf_var_always_blocked(self, guard_relaxed):
        """rm -rf /var 始终拒绝"""
        cmd = "rm -rf /var"
        r = guard_relaxed.check_command(cmd)
        assert not r["allowed"], f"rm -rf /var should be blocked: {r}"

    def test_rm_rf_star_root_always_blocked(self, guard_relaxed):
        """rm -rf /* 始终拒绝"""
        cmd = "rm -rf /*"
        r = guard_relaxed.check_command(cmd)
        assert not r["allowed"], f"rm -rf /* should be blocked: {r}"


# ═══════════════════════════════════════════════════════════════
# chmod/chown 放宽测试
# ═══════════════════════════════════════════════════════════════

class TestChmodRelaxation:
    """chmod/chown 的项目外限制在 relax 模式下应放开"""

    def test_chmod_outside_project_relaxed(self, guard_relaxed):
        """restrict_to_project=False 时，chmod /usr/local/bin/xxx 应允许"""
        cmd = "chmod 755 /usr/local/bin/somescript"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed should allow chmod outside project: {r}"

    def test_chown_outside_project_relaxed(self, guard_relaxed):
        """restrict_to_project=False 时，chown 项目外应允许"""
        cmd = "chown user /tmp/somefile"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"relaxed should allow chown outside project: {r}"


# ═══════════════════════════════════════════════════════════════
# 危险模式默认 log 行为
# ═══════════════════════════════════════════════════════════════

class TestDangerousPatternsLogMode:
    """dangerous_command_action=log 时危险命令只记录不拒绝"""

    def test_format_logged_not_rejected(self, guard_relaxed):
        """format 应只记录，不拒绝"""
        r = guard_relaxed.check_command("format /dev/sda")
        assert r["allowed"], f"format should be logged not rejected: {r}"
        assert "logged" in r.get("reason", "").lower()

    def test_dd_logged_not_rejected(self, guard_relaxed):
        """dd 应只记录"""
        r = guard_relaxed.check_command("dd if=/dev/zero of=/dev/sda")
        assert r["allowed"], f"dd should be logged: {r}"

    def test_mkfs_logged_not_rejected(self, guard_relaxed):
        """mkfs 应只记录"""
        r = guard_relaxed.check_command("mkfs.ext4 /dev/sda")
        assert r["allowed"], f"mkfs should be logged: {r}"

    def test_shutdown_logged_not_rejected(self, guard_relaxed):
        """shutdown 应只记录"""
        r = guard_relaxed.check_command("shutdown -h now")
        assert r["allowed"], f"shutdown should be logged: {r}"

    def test_reboot_logged_not_rejected(self, guard_relaxed):
        """reboot 应只记录"""
        r = guard_relaxed.check_command("reboot now")
        assert r["allowed"], f"reboot should be logged: {r}"


# ═══════════════════════════════════════════════════════════════
# 综合场景（用户原始问题场景）
# ═══════════════════════════════════════════════════════════════

class TestRealWorldScenarios:
    """真实使用场景"""

    def test_curl_pipe_python_with_ellipsis(self, guard_relaxed):
        """用户的原始场景：curl | python3 -c '...' 包含 ..."""
        cmd = (
            "curl -sL 'https://example.com' -A 'Mozilla/5.0' "
            "--connect-timeout 10 --max-time 15 2>&1 | "
            "python3 -c \"import sys; content = sys.stdin.read(); "
            "print('No chapters found, searching...')\""
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"user's original scenario should pass: {r}"

    def test_curl_pipe_python_with_regex_path_like(self, guard_relaxed):
        """Python 正则中含有 </h1> 的 / 字符"""
        cmd = (
            "curl example.com | "
            "python3 -c \"import re; "
            "m = re.search(r'<h1[^>]*>(.*?)</h1>', ''); "
            "print('done')\""
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"regex with / should not cause issues: {r}"

    def test_python_c_os_system_should_still_be_checked(self, guard_relaxed):
        """is_safe_python_code 仍应拦截 os.system（独立 Python 代码检查）"""
        # 注意：check_command 不检查 python -c 的代码内容，
        # is_safe_python_code 在 code_tools.py 中单独调用。
        # 这里只确认 command 层面放行
        cmd = "python3 -c \"import os; os.system('ls')\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"python -c should pass command check: {r}"

    def test_git_commands_work(self, guard_relaxed):
        """git 命令应正常工作"""
        for cmd in [
            "git status",
            "git log --oneline -10",
            "git diff HEAD~1",
            "git add .",
        ]:
            r = guard_relaxed.check_command(cmd)
            assert r["allowed"], f"git cmd should work: {cmd} -> {r}"

    def test_adb_commands_work(self, guard_relaxed):
        """包管理命令应工作"""
        for cmd in [
            "pip list",
            "npm ls",
            "brew info python",
            "cargo build",
        ]:
            r = guard_relaxed.check_command(cmd)
            assert r["allowed"], f"pkg cmd should work: {cmd} -> {r}"

    def test_pipe_mixed_with_dangerous_rejected(self, guard_strict):
        """管道中混入 rm -rf /etc 整体拒绝"""
        cmd = "echo hello | rm -rf /etc/test"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"dangerous rm in pipe should reject: {r}"

    def test_users_exact_scenario_curl_pipe_python3_multiline(
        self, guard_relaxed
    ):
        """用户实际场景：curl | python3 -c 多行内联脚本包含注释和正则"""
        cmd = (
            'curl -sL "https://www.ituring.com.cn/book/2658" '
            '-A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" '
            '--connect-timeout 10 --max-time 15 2>&1 | '
            'python3 -c "\nimport sys, re, html\n'
            'content = sys.stdin.read()\n'
            '# 找目录\n'
            'toc_match = re.search(r\'目录(.*?)(?:作者|出版|ISBN)\', content, re.DOTALL)\n'
            "print('done')\""
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], (
            f"users exact scenario should pass: {r.get('reason', r)}"
        )

    def test_pipeline_recursive_depth(self, guard_relaxed):
        """三层管道应正常拆分检查"""
        cmd = "cat file.txt | grep foo | sort | uniq"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"triple pipe should work: {r}"

    def test_pipeline_with_semicolon(self, guard_relaxed):
        """分号分隔的两个命令应独立检查"""
        cmd = "echo hello; echo world"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"semicolon should split: {r}"

    def test_pipeline_with_double_ampersand(self, guard_relaxed):
        """&& 分隔的两个命令应独立检查"""
        cmd = "mkdir test && cd test"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"&& should split: {r}"

    def test_pipeline_with_double_pipe(self, guard_relaxed):
        """|| 分隔的两个命令应独立检查"""
        cmd = "false || echo fallback"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"|| should split: {r}"

    def test_pipeline_dangerous_in_first_strict(self, guard_strict):
        """管道第一个命令是危险的，严格模式下整体拒绝"""
        cmd = "rm -rf /etc/test | echo done"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"dangerous first cmd in pipe should reject: {r}"

    def test_comment_inside_python_c_not_stripped(self, guard_relaxed):
        """python3 -c 中的 # 注释行不应被剥离"""
        cmd = 'python3 -c "# this is code\nprint(1)"'
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"comment in -c should be preserved: {r}"

    def test_leading_comment_then_pipeline(self, guard_relaxed):
        """前导注释行后跟管道命令"""
        cmd = "# Fetch data\ncurl example.com | python3 -c \"print('ok')\""
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"leading comment + pipe should work: {r}"

    def test_sudo_relaxed_allows_make(self, guard_relaxed):
        """restrict_to_project=False 时，sudo make 应允许"""
        r = guard_relaxed.check_command("sudo make install")
        assert r["allowed"], f"relaxed sudo make should pass: {r}"

    def test_sudo_relaxed_allows_whoami(self, guard_relaxed):
        """restrict_to_project=False 时，sudo whoami 应允许"""
        r = guard_relaxed.check_command("sudo whoami")
        assert r["allowed"], f"relaxed sudo whoami should pass: {r}"


# ═══════════════════════════════════════════════════════════════
# Heredoc 不误拆测试
# ═══════════════════════════════════════════════════════════════


class TestHeredocNotSplit:
    """heredoc 内容中的 |, ;, &&, || 不应被当作管道分隔符"""

    def test_heredoc_with_table_pipes(self, guard_relaxed):
        """heredoc 内 markdown 表格的 | 不应拆分管道"""
        cmd = (
            'python3 -c "import sys; print(sys.stdin.read())" '
            "<< 'EOF'\n"
            "| Name | Value |\n"
            "|------|-------|\n"
            "| foo  | bar   |\n"
            "EOF"
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"heredoc with table pipes should not split: {r}"

    def test_heredoc_with_semicolons(self, guard_relaxed):
        """heredoc 内的 ; 不应拆分命令"""
        cmd = (
            'python3 -c "print(1)" << EOF\n'
            "echo a; echo b; echo c\n"
            "EOF"
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"heredoc with semicolons should not split: {r}"

    def test_heredoc_with_double_ampersand(self, guard_relaxed):
        """heredoc 内的 && 不应拆分命令"""
        cmd = (
            'python3 -c "print(1)" << EOF\n'
            "cmd1 && cmd2\n"
            "EOF"
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"heredoc with && should not split: {r}"

    def test_heredoc_with_chinese_and_pipes(self, guard_relaxed):
        """heredoc 内中文文本和表格管道不应被拆分"""
        cmd = (
            'python3 -c "import sys; sys.stdin.read()" '
            "<< 'PYEOF'\n"
            "| 项目 | 内容 |\n"
            "|------|------|\n"
            "| 书名 | AI书 |\n"
            "PYEOF"
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"heredoc with chinese and pipes should not split: {r}"

    def test_pipe_after_heredoc_should_split(self, guard_relaxed):
        """heredoc 结束后的 | 应正常拆分管道"""
        cmd = (
            'cat << EOF\nhello\nEOF\n'
            "| grep hello"
        )
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"pipe after heredoc should still work: {r}"

    def test_heredoc_pipe_mixed_dangerous_in_second(self, guard_strict):
        """heredoc 后跟管道，第二段有危险命令应拒绝"""
        cmd = (
            'cat << EOF\nhello\nEOF\n'
            "| rm -rf /etc/test"
        )
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"dangerous cmd after heredoc pipe should be rejected: {r}"


# ═══════════════════════════════════════════════════════════════
# 新增白名单命令测试
# ═══════════════════════════════════════════════════════════════


class TestNewWhitelistCommands:
    """新加入白名单的命令应正常工作"""

    # ── macOS 命令 ──

    def test_macos_pbcopy(self, guard_relaxed):
        r = guard_relaxed.check_command("echo hello | pbcopy")
        assert r["allowed"], f"pbcopy should be allowed: {r}"

    def test_macos_pbpaste(self, guard_relaxed):
        r = guard_relaxed.check_command("pbpaste")
        assert r["allowed"], f"pbpaste should be allowed: {r}"

    def test_macos_xcodebuild(self, guard_relaxed):
        r = guard_relaxed.check_command("xcodebuild -list")
        assert r["allowed"], f"xcodebuild should be allowed: {r}"

    def test_macos_xcode_select(self, guard_relaxed):
        r = guard_relaxed.check_command("xcode-select -p")
        assert r["allowed"], f"xcode-select should be allowed: {r}"

    def test_macos_simctl(self, guard_relaxed):
        r = guard_relaxed.check_command("simctl list devices")
        assert r["allowed"], f"simctl should be allowed: {r}"

    def test_macos_codesign(self, guard_relaxed):
        r = guard_relaxed.check_command("codesign -vv myapp.app")
        assert r["allowed"], f"codesign should be allowed: {r}"

    def test_macos_security(self, guard_relaxed):
        r = guard_relaxed.check_command("security find-certificate -a")
        assert r["allowed"], f"security should be allowed: {r}"

    def test_macos_say(self, guard_relaxed):
        r = guard_relaxed.check_command("say hello")
        assert r["allowed"], f"say should be allowed: {r}"

    def test_macos_screencapture(self, guard_relaxed):
        r = guard_relaxed.check_command("screencapture test.png")
        assert r["allowed"], f"screencapture should be allowed: {r}"

    def test_macos_softwareupdate(self, guard_relaxed):
        r = guard_relaxed.check_command("softwareupdate --list")
        assert r["allowed"], f"softwareupdate should be allowed: {r}"

    def test_macos_system_profiler(self, guard_relaxed):
        r = guard_relaxed.check_command("system_profiler SPHardwareDataType")
        assert r["allowed"], f"system_profiler should be allowed: {r}"

    def test_macos_networksetup(self, guard_relaxed):
        r = guard_relaxed.check_command("networksetup -listallhardwareports")
        assert r["allowed"], f"networksetup should be allowed: {r}"

    def test_macos_scutil(self, guard_relaxed):
        r = guard_relaxed.check_command("scutil --dns")
        assert r["allowed"], f"scutil should be allowed: {r}"

    def test_macos_mdls(self, guard_relaxed):
        r = guard_relaxed.check_command("mdls somefile.txt")
        assert r["allowed"], f"mdls should be allowed: {r}"

    def test_macos_mdutil(self, guard_relaxed):
        r = guard_relaxed.check_command("mdutil -s /")
        assert r["allowed"], f"mdutil should be allowed: {r}"

    def test_macos_qlmanage(self, guard_relaxed):
        r = guard_relaxed.check_command("qlmanage -p somefile.txt")
        assert r["allowed"], f"qlmanage should be allowed: {r}"

    def test_macos_pkgutil(self, guard_relaxed):
        r = guard_relaxed.check_command("pkgutil --pkgs")
        assert r["allowed"], f"pkgutil should be allowed: {r}"

    def test_macos_installer(self, guard_relaxed):
        r = guard_relaxed.check_command("installer -pkg test.pkg -target /")
        assert r["allowed"], f"installer should be allowed: {r}"

    def test_macos_pluginkit(self, guard_relaxed):
        r = guard_relaxed.check_command("pluginkit -m")
        assert r["allowed"], f"pluginkit should be allowed: {r}"

    # ── Windows 命令 ──

    def test_windows_winget(self, guard_relaxed):
        r = guard_relaxed.check_command("winget list")
        assert r["allowed"], f"winget should be allowed: {r}"

    def test_windows_explorer(self, guard_relaxed):
        r = guard_relaxed.check_command("explorer .")
        assert r["allowed"], f"explorer should be allowed: {r}"

    def test_windows_sfc(self, guard_relaxed):
        r = guard_relaxed.check_command("sfc /scannow")
        assert r["allowed"], f"sfc should be allowed: {r}"

    def test_windows_dism(self, guard_relaxed):
        r = guard_relaxed.check_command("dism /online /get-packages")
        assert r["allowed"], f"dism should be allowed: {r}"

    def test_windows_chkdsk(self, guard_relaxed):
        r = guard_relaxed.check_command("chkdsk C:")
        assert r["allowed"], f"chkdsk should be allowed: {r}"

    def test_windows_driverquery(self, guard_relaxed):
        r = guard_relaxed.check_command("driverquery")
        assert r["allowed"], f"driverquery should be allowed: {r}"

    def test_windows_route(self, guard_relaxed):
        r = guard_relaxed.check_command("route print")
        assert r["allowed"], f"route should be allowed: {r}"

    def test_windows_powercfg(self, guard_relaxed):
        r = guard_relaxed.check_command("powercfg /list")
        assert r["allowed"], f"powercfg should be allowed: {r}"

    def test_windows_getmac(self, guard_relaxed):
        r = guard_relaxed.check_command("getmac")
        assert r["allowed"], f"getmac should be allowed: {r}"

    def test_windows_takeown(self, guard_relaxed):
        r = guard_relaxed.check_command("takeown /f somefile")
        assert r["allowed"], f"takeown should be allowed: {r}"

    def test_windows_runas(self, guard_relaxed):
        r = guard_relaxed.check_command("runas /user:admin cmd")
        assert r["allowed"], f"runas should be allowed: {r}"

    def test_windows_msiexec(self, guard_relaxed):
        r = guard_relaxed.check_command("msiexec /i package.msi")
        assert r["allowed"], f"msiexec should be allowed: {r}"

    def test_windows_wsl(self, guard_relaxed):
        r = guard_relaxed.check_command("wsl ls -la")
        assert r["allowed"], f"wsl should be allowed: {r}"

    # ── 其他 CLI 工具 ──

    def test_glab(self, guard_relaxed):
        r = guard_relaxed.check_command("glab repo list")
        assert r["allowed"], f"glab should be allowed: {r}"

    def test_hub(self, guard_relaxed):
        r = guard_relaxed.check_command("hub clone user/repo")
        assert r["allowed"], f"hub should be allowed: {r}"

    def test_act(self, guard_relaxed):
        r = guard_relaxed.check_command("act -l")
        assert r["allowed"], f"act should be allowed: {r}"

    def test_miller(self, guard_relaxed):
        r = guard_relaxed.check_command("miller --csv cat data.csv")
        assert r["allowed"], f"miller/mlr should be allowed: {r}"

    def test_dot(self, guard_relaxed):
        r = guard_relaxed.check_command("dot -Tpng input.dot -o out.png")
        assert r["allowed"], f"dot should be allowed: {r}"

    def test_sox(self, guard_relaxed):
        r = guard_relaxed.check_command("sox input.wav output.mp3")
        assert r["allowed"], f"sox should be allowed: {r}"


# ═══════════════════════════════════════════════════════════════
# sed/awk 路径误判测试
# ═══════════════════════════════════════════════════════════════


class TestSedAwkPatternNotPath:
    """sed/awk 的模式/脚本参数不应被误判为文件路径"""

    def test_sed_address_range(self, guard_relaxed):
        """用户实际场景：sed -n '/start/,//p'"""
        cmd = "sed -n '/三、数学组件/,//p'"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"sed address range should work: {r}"

    def test_sed_substitute(self, guard_relaxed):
        """sed 's/old/new/'"""
        cmd = "sed 's/foo/bar/' file.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"sed substitute should work: {r}"

    def test_sed_delete_pattern(self, guard_relaxed):
        """sed '/pattern/d'"""
        cmd = "sed '/debug/d' log.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"sed delete pattern should work: {r}"

    def test_sed_with_e_flag(self, guard_relaxed):
        """sed -e 's/foo/bar/'"""
        cmd = "sed -e 's/foo/bar/' file.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"sed -e should work: {r}"

    def test_awk_pattern(self, guard_relaxed):
        """awk '/pattern/'"""
        cmd = "awk '/error/' log.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"awk pattern should work: {r}"

    def test_awk_with_action(self, guard_relaxed):
        """awk '{print $1}'"""
        cmd = "awk '{print $1}' data.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"awk with action should work: {r}"

    def test_sed_in_pipe(self, guard_relaxed):
        """管道中的 sed 命令"""
        cmd = "cat /tmp/log.txt | sed -n '/三、数学组件/,//p'"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"sed in pipe should work: {r}"

    def test_sed_in_place_still_checked(self, guard_strict):
        """sed -i 修改系统文件仍应被路径拦截（严格模式）"""
        cmd = "sed -i 's/foo/bar/' /etc/hosts"
        r = guard_strict.check_command(cmd)
        # sed 在 output_only_commands，跳过路径检查
        # 但严格模式下不应修改系统文件 — 这个由 restrict_to_project 控制
        # output_only 跳过所有路径检查，包括系统文件
        # 这是预期行为：sed 是纯文本工具
        assert r["allowed"], f"sed -i should be allowed as output-only tool: {r}"

    def test_locate_pattern(self, guard_relaxed):
        """locate 的搜索模式不应被当作路径"""
        cmd = "locate /srv/config"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"locate should work: {r}"

    def test_mlocate_pattern(self, guard_relaxed):
        """mlocate 的搜索模式不应被当作路径"""
        cmd = "mlocate /var/data"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"mlocate should work: {r}"


# ═══════════════════════════════════════════════════════════════
# 子命令委托测试 — git grep 等的 /pattern/ 参数
# ═══════════════════════════════════════════════════════════════


class TestSubCommandDelegation:
    """元命令的子命令若是 output_only，应跳过路径检查"""

    def test_git_grep_pattern(self, guard_relaxed):
        """git grep '/pattern/' 应通过（grep 是 output_only）"""
        cmd = "git grep '/error/' src/"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"git grep should delegate: {r}"

    def test_git_log_output_only(self, guard_relaxed):
        """git log 是只读操作"""
        cmd = "git log --oneline -10"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"git log should work: {r}"

    def test_git_diff_is_readonly(self, guard_relaxed):
        """git diff 是只读操作"""
        cmd = "git diff HEAD~1"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"git diff should work: {r}"

    def test_git_checkout_still_checked(self, guard_strict):
        """git checkout 不是 output_only，路径仍应检查"""
        cmd = "git checkout /some/branch"
        r = guard_strict.check_command(cmd)
        # checkout 不在 output_only，会走路径检查
        # /some/branch 不是安全路径，会被拒绝
        assert not r["allowed"], f"git checkout outside should be rejected: {r}"

    def test_git_grep_with_global_flag(self, guard_relaxed):
        """git -C /tmp grep '/pattern/' — 全局标志后的子命令"""
        cmd = "git -C /tmp grep '/error/' file.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"git -C grep should delegate: {r}"

    def test_docker_ps_no_path_issue(self, guard_relaxed):
        """docker ps 参数不以 / 开头，本来就不触发路径检查"""
        cmd = "docker ps -a"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"docker ps should work: {r}"

    def test_git_grep_pipe_strict(self, guard_strict):
        """git grep ... | sed ... 严格模式下也应通过"""
        cmd = "git grep '/pattern/' | sed 's/foo/bar/'"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"git grep pipe should work: {r}"


# ═══════════════════════════════════════════════════════════════
# rm -f 误判为 rm -rf 修复测试
# ═══════════════════════════════════════════════════════════════

class TestRmForceNotRecursive:
    """rm -f (不带 -r) 不应被当作递归删除拦截"""

    def test_rm_f_without_r_is_safe_strict(self, guard_strict):
        """rm -f file (没有 -r) 应允许"""
        cmd = "rm -f somefile.txt"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm -f should be allowed (not recursive): {r}"

    def test_rm_f_without_r_is_safe_relaxed(self, guard_relaxed):
        """rm -f file (没有 -r) 在宽松模式下应允许"""
        cmd = "rm -f somefile.txt"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"rm -f should be allowed: {r}"

    def test_rm_f_multiple_files_safe(self, guard_strict):
        """rm -f file1 file2 (没有 -r) 应允许"""
        cmd = "rm -f a.txt b.txt c.txt"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm -f multiple files should be allowed: {r}"

    def test_rm_nothing_safe(self, guard_strict):
        """rm file (无任何选项) 应允许"""
        cmd = "rm myfile.txt"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm without flags should be allowed: {r}"

    def test_rm_rf_system_path_still_blocked(self, guard_strict):
        """rm -rf /etc 仍应拒绝"""
        cmd = "rm -rf /etc"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"rm -rf /etc should still be blocked: {r}"

    def test_rm_r_still_flagged_as_recursive(self, guard_strict):
        """rm -r dir (有 -r) 仍应通过递归检查"""
        cmd = "rm -r ./subdir"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm -r in project should be allowed: {r}"

    def test_rm_rf_still_flagged_as_recursive(self, guard_strict):
        """rm -rf dir (有 -rf) 仍应通过递归检查"""
        cmd = "rm -rf ./subdir"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm -rf in project should be allowed: {r}"

    def test_rm_recursive_long_flag(self, guard_strict):
        """rm --recursive dir 仍应被识别为递归"""
        cmd = "rm --recursive ./subdir"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm --recursive should be allowed: {r}"

    def test_rm_fr_still_recursive(self, guard_strict):
        """rm -fr dir (选项顺序不同) 仍应被识别为递归"""
        cmd = "rm -fr ./subdir"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"rm -fr should still be recognized as recursive: {r}"

    def test_rm_f_without_r_root_still_blocked(self, guard_strict):
        """rm -f / (没有 -r 但目标是系统路径) 仍应被拒绝"""
        cmd = "rm -f /"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"rm -f / should be blocked (dangerous target): {r}"


# ═══════════════════════════════════════════════════════════════
# chmod +x 等良性操作不应被拦截
# ═══════════════════════════════════════════════════════════════

class TestChmodBenignOps:
    """chmod +x / -x / 644 / 755 等良性操作应直接允许"""

    def test_chmod_plus_x_allowed_strict(self, guard_strict):
        """chmod +x (添加可执行权限) 应允许"""
        cmd = "chmod +x script.sh"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod +x should be allowed: {r}"

    def test_chmod_minus_x_allowed_strict(self, guard_strict):
        """chmod -x (移除可执行权限) 应允许"""
        cmd = "chmod -x script.sh"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod -x should be allowed: {r}"

    def test_chmod_numeric_644_allowed_strict(self, guard_strict):
        """chmod 644 (正常权限) 应允许"""
        cmd = "chmod 644 file.txt"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod 644 should be allowed: {r}"

    def test_chmod_numeric_755_allowed_strict(self, guard_strict):
        """chmod 755 (正常可执行权限) 应允许"""
        cmd = "chmod 755 script.py"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod 755 should be allowed: {r}"

    def test_chmod_ugo_plus_x_allowed_strict(self, guard_strict):
        """chmod u+x 应允许"""
        cmd = "chmod u+x script.sh"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod u+x should be allowed: {r}"

    def test_chmod_777_still_warns(self, guard_strict):
        """chmod 777 (权限过于宽松) 在交互模式下会要求确认，
        非交互模式下应直接允许(因为已在项目内)"""
        cmd = "chmod 777 dangerous.sh"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod 777 in project should be allowed in non-interactive: {r}"

    def test_chmod_system_path_blocked_strict(self, guard_strict):
        """chmod 在系统路径上仍应拒绝"""
        cmd = "chmod +x /usr/bin/somescript"
        r = guard_strict.check_command(cmd)
        assert not r["allowed"], f"chmod on system path should be blocked: {r}"

    def test_chmod_recursive_still_checked(self, guard_strict):
        """chmod -R (递归) 在交互模式下需要确认，
        非交互模式下允许"""
        cmd = "chmod -R 755 ./dir"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chmod -R in project non-interactive should allow: {r}"

    def test_chown_still_requires_confirmation(self, guard_strict):
        """chown 在交互模式下仍需要确认，
        非交互模式下允许"""
        cmd = "chown user file.txt"
        r = guard_strict.check_command(cmd)
        assert r["allowed"], f"chown in project non-interactive should allow: {r}"

    def test_chmod_system_path_blocked_relaxed(self, guard_relaxed):
        """宽松模式下 chmod 系统路径应允许"""
        cmd = "chmod +x /usr/local/bin/somescript"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"chmod on system path should be allowed in relaxed: {r}"

    def test_chmod_777_relaxed_still_checks(self, guard_relaxed):
        """宽松模式下 chmod 777 在交互时需确认，非交互应允许"""
        cmd = "chmod 777 dangerous.sh"
        r = guard_relaxed.check_command(cmd)
        assert r["allowed"], f"chmod 777 relaxed non-interactive should allow: {r}"
