"""
测试 atomic_tools.py —— run_shell 超时、任务清理、健壮性
"""
import asyncio
import sys
import tempfile
from pathlib import Path

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

import pytest
from aacode.tools.atomic_tools import AtomicTools
from aacode.utils.safety import SafetyGuard


@pytest.fixture
def atomic_tools():
    with tempfile.TemporaryDirectory() as td:
        project_path = Path(td)
        safety_guard = SafetyGuard(project_path)
        tools = AtomicTools(project_path, safety_guard)
        yield tools


class TestAtomicToolsRunShell:
    """run_shell 超时、任务清理、健壮性"""

    @pytest.mark.asyncio
    async def test_normal_command(self, atomic_tools):
        result = await atomic_tools.run_shell("echo hello", timeout=10)
        assert result["success"] is True
        assert result["returncode"] == 0
        assert "hello" in result["stdout"]
        assert "error" not in result

    @pytest.mark.asyncio
    async def test_timeout_returns_proper_error(self, atomic_tools):
        result = await atomic_tools.run_shell("sleep 10", timeout=1)
        assert result["success"] is True
        assert result.get("timeout") is True
        assert "timeout" in result["error"].lower()

    @pytest.mark.asyncio
    async def test_system_usable_after_timeout(self, atomic_tools):
        for i in range(3):
            await atomic_tools.run_shell("sleep 10", timeout=1)
        result = await atomic_tools.run_shell("echo after_timeout", timeout=10)
        assert result["success"] is True
        assert result["returncode"] == 0
        assert "after_timeout" in result["stdout"]

    @pytest.mark.asyncio
    async def test_command_with_stdin_input(self, atomic_tools):
        result = await atomic_tools.run_shell(
            "cat", stdin_input="hello stdin", timeout=10
        )
        assert result["success"] is True
        assert "hello stdin" in result["stdout"]

    @pytest.mark.asyncio
    async def test_failed_command_returns_exit_code(self, atomic_tools):
        result = await atomic_tools.run_shell(
            "python3 -c \"import sys; sys.exit(42)\"", timeout=10
        )
        assert result["success"] is True
        assert result["returncode"] == 42

    @pytest.mark.asyncio
    async def test_large_output_not_truncated_by_default(self, atomic_tools):
        result = await atomic_tools.run_shell(
            "python3 -c \"for i in range(1000): print(f'line {i}')\"",
            timeout=30,
        )
        assert result["success"] is True
        assert len(result["stdout"]) > 5000

    @pytest.mark.asyncio
    async def test_max_output_truncation(self, atomic_tools):
        result = await atomic_tools.run_shell(
            "python3 -c \"for i in range(1000): print(f'line {i}')\"",
            timeout=30,
            max_output=200,
        )
        assert result["success"] is True
        assert result["stdout_truncated"] is True
        # 内容被截断，且末尾追加了文件路径引导
        assert "[Full output saved to" in result["stdout"]
        assert "Grep the file" in result["stdout"]
        # 截断后约 200 字符 + 后缀，不应接近原始 1000 行
        assert result["stdout"].count("\n") < 50

    @pytest.mark.asyncio
    async def test_stderr_captured(self, atomic_tools):
        result = await atomic_tools.run_shell(
            "python3 -c \"import sys; sys.stderr.write('err msg')\"",
            timeout=10,
        )
        assert result["success"] is True
        assert "err msg" in result["stderr"]

    @pytest.mark.asyncio
    async def test_return_fields_on_timeout(self, atomic_tools):
        result = await atomic_tools.run_shell("sleep 30", timeout=1)
        assert "command" in result
        assert "working_directory" in result
        assert result["command"] == "sleep 30"

    @pytest.mark.asyncio
    async def test_multiple_concurrent_commands(self, atomic_tools):
        async def run(cmd, t):
            return await atomic_tools.run_shell(cmd, timeout=t)

        r1, r2 = await asyncio.gather(
            run("echo first", 10),
            run("echo second", 10),
        )
        assert r1["stdout"].strip() == "first"
        assert r2["stdout"].strip() == "second"

    @pytest.mark.asyncio
    async def test_safety_guard_rejects_dangerous_command(self, atomic_tools):
        result = await atomic_tools.run_shell("rm -rf /", timeout=10)
        assert result.get("allowed") is False
        assert "rejected" in result.get("error", "").lower()
