"""PTYSession 单元测试

测试场景：
- PTY 会话启动和关闭
- 交互式命令执行
- 自动应答提示
- 超时处理
- 自定义应答规则
"""

from __future__ import annotations

import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from agentkit.tools.pty_session import PTYSession, PTYOutput
from agentkit.tools.shell import ShellTool


class TestPTYSessionConstruction:
    """测试 PTYSession 构造"""

    def test_default_construction(self):
        pty = PTYSession()
        assert pty.is_running is False
        assert pty._auto_respond is False
        assert pty._default_timeout == 30.0

    def test_custom_construction(self):
        pty = PTYSession(
            auto_respond=False,
            custom_rules=[(r"confirm\?", "yes")],
            default_timeout=60.0,
        )
        assert pty._auto_respond is False
        assert len(pty._respond_rules) > len(pty._respond_rules) - 1
        assert pty._default_timeout == 60.0


class TestPTYSessionLifecycle:
    """测试 PTYSession 生命周期"""

    @pytest.mark.asyncio
    async def test_start_and_close(self):
        """启动和关闭 PTY 会话"""
        pty = PTYSession()
        await pty.start()
        assert pty.is_running is True
        await pty.close()
        assert pty.is_running is False

    @pytest.mark.asyncio
    async def test_start_idempotent(self):
        """重复启动不报错"""
        pty = PTYSession()
        await pty.start()
        await pty.start()  # 不应抛出异常
        assert pty.is_running is True
        await pty.close()

    @pytest.mark.asyncio
    async def test_close_without_start(self):
        """未启动时关闭不报错"""
        pty = PTYSession()
        await pty.close()  # 不应抛出异常


class TestPTYSessionExecution:
    """测试 PTYSession 命令执行"""

    @pytest.mark.asyncio
    async def test_run_simple_command(self):
        """执行简单命令"""
        pty = PTYSession(default_timeout=10.0)
        try:
            await pty.start()
            result = await pty.run_command("echo hello_pty")
            assert "hello_pty" in result.output
            assert result.exit_code == 0
            assert result.timed_out is False
        finally:
            await pty.close()

    @pytest.mark.asyncio
    async def test_run_command_with_cwd(self):
        """指定工作目录执行命令"""
        pty = PTYSession(default_timeout=10.0)
        try:
            await pty.start()
            result = await pty.run_command("pwd", cwd="/tmp")
            assert "/tmp" in result.output
        finally:
            await pty.close()

    @pytest.mark.asyncio
    async def test_run_command_with_env(self):
        """指定环境变量执行命令"""
        pty = PTYSession(default_timeout=10.0)
        try:
            await pty.start()
            result = await pty.run_command(
                "echo $PTY_TEST_VAR",
                env={"PTY_TEST_VAR": "pty_value"},
            )
            assert "pty_value" in result.output
        finally:
            await pty.close()

    @pytest.mark.asyncio
    async def test_run_failing_command(self):
        """执行失败命令"""
        pty = PTYSession(default_timeout=10.0)
        try:
            await pty.start()
            result = await pty.run_command("ls /nonexistent_dir_xyz_12345")
            assert result.exit_code != 0
        finally:
            await pty.close()

    @pytest.mark.asyncio
    async def test_run_command_timeout(self):
        """命令超时"""
        pty = PTYSession(default_timeout=10.0)
        try:
            await pty.start()
            result = await pty.run_command("sleep 30", timeout=0.5)
            assert result.timed_out is True
            assert result.exit_code == -1
        finally:
            await pty.close()


class TestPTYSessionAutoRespond:
    """测试 PTYSession 自动应答"""

    @pytest.mark.asyncio
    async def test_auto_respond_yes_no(self):
        """自动应答 [y/N] 提示"""
        # 使用 echo 模拟包含提示的输出，然后验证自动应答规则存在
        pty = PTYSession(auto_respond=True)
        # 验证规则已加载
        rule_patterns = [r[0] for r in pty._respond_rules]
        assert any("y/N" in p or "Y/n" in p for p in rule_patterns)

    @pytest.mark.asyncio
    async def test_auto_respond_disabled(self):
        """禁用自动应答"""
        pty = PTYSession(auto_respond=False)
        assert pty._auto_respond is False

    @pytest.mark.asyncio
    async def test_custom_respond_rules(self):
        """自定义应答规则"""
        pty = PTYSession(
            auto_respond=True,
            custom_rules=[(r"continue\?\s*$", "yes")],
        )
        rule_patterns = [r[0] for r in pty._respond_rules]
        assert r"continue\?\s*$" in rule_patterns


class TestPTYSessionSendAndRead:
    """测试 PTYSession 发送和读取"""

    @pytest.mark.asyncio
    async def test_send_without_start(self):
        """未启动时发送不报错"""
        pty = PTYSession()
        await pty.send("test")  # 不应抛出异常

    @pytest.mark.asyncio
    async def test_read_output_without_start(self):
        """未启动时读取返回空"""
        pty = PTYSession()
        output = await pty.read_output()
        assert output == ""


class TestPTYOutput:
    """测试 PTYOutput 数据类"""

    def test_default_values(self):
        output = PTYOutput(output="test")
        assert output.output == "test"
        assert output.exit_code == -1
        assert output.timed_out is False

    def test_custom_values(self):
        output = PTYOutput(output="error", exit_code=1, timed_out=True)
        assert output.exit_code == 1
        assert output.timed_out is True


class TestShellToolInteractiveMode:
    """测试 ShellTool 交互式模式"""

    @pytest.mark.asyncio
    async def test_interactive_mode(self):
        """ShellTool interactive 模式执行命令"""
        tool = ShellTool()
        result = await tool.execute(command="echo interactive_test", interactive=True)
        assert result["exit_code"] == 0
        assert "interactive_test" in result["output"]

    @pytest.mark.asyncio
    async def test_interactive_mode_with_session(self):
        """ShellTool 会话模式 + 交互式"""
        tool = ShellTool()
        result = await tool.execute(
            command="echo session_interactive",
            session_id="int-session",
            interactive=True,
        )
        assert result["exit_code"] == 0
        assert "session_interactive" in result["output"]
