from __future__ import annotations

from unittest.mock import MagicMock, patch

import pytest
from hypothesis import given, settings
from hypothesis import strategies as st

from claude_teams.acpx_client import (
    AcpxError,
    close_session,
    create_session,
    get_session_status,
    send_prompt,
)

# ---------------------------------------------------------------------------
# 辅助函数
# ---------------------------------------------------------------------------

_AGENT_ST = st.text(min_size=1, max_size=20, alphabet=st.characters(whitelist_categories=("Ll", "Lu", "Nd"), whitelist_characters="-_"))
_SESSION_ST = st.text(min_size=1, max_size=40, alphabet=st.characters(whitelist_categories=("Ll", "Lu", "Nd"), whitelist_characters="-_@"))


def _make_completed_process(returncode: int = 0, stdout: str = "", stderr: str = "") -> MagicMock:
    result = MagicMock()
    result.returncode = returncode
    result.stdout = stdout
    result.stderr = stderr
    return result


# ---------------------------------------------------------------------------
# 属性测试 1.1：AcpxError 错误传播
# Feature: acpx-backend-migration, Property 1: AcpxError 错误传播
# 验证：需求 1.5
# ---------------------------------------------------------------------------

class TestProperty1AcpxErrorPropagation:
    """对任意 agent/session_name 组合，mock subprocess 返回非零码，验证 AcpxError 被抛出"""

    @given(agent=_AGENT_ST, session_name=_SESSION_ST, returncode=st.integers(min_value=1, max_value=255))
    @settings(max_examples=100)
    def test_create_session_raises_on_nonzero_exit(self, agent: str, session_name: str, returncode: int) -> None:
        # Feature: acpx-backend-migration, Property 1: AcpxError 错误传播
        with patch("subprocess.run", return_value=_make_completed_process(returncode=returncode, stderr="error")):
            with pytest.raises(AcpxError):
                create_session(agent, session_name, cwd="/tmp")

    @given(agent=_AGENT_ST, session_name=_SESSION_ST, returncode=st.integers(min_value=1, max_value=255))
    @settings(max_examples=100)
    def test_send_prompt_raises_on_nonzero_exit(self, agent: str, session_name: str, returncode: int) -> None:
        # Feature: acpx-backend-migration, Property 1: AcpxError 错误传播
        with patch("subprocess.run", return_value=_make_completed_process(returncode=returncode, stderr="error")):
            with pytest.raises(AcpxError):
                send_prompt(agent, session_name, "hello", name="worker", team_name="team", cwd="/tmp")

    @given(agent=_AGENT_ST, session_name=_SESSION_ST, returncode=st.integers(min_value=1, max_value=255))
    @settings(max_examples=100)
    def test_close_session_raises_on_nonzero_exit(self, agent: str, session_name: str, returncode: int) -> None:
        # Feature: acpx-backend-migration, Property 1: AcpxError 错误传播
        with patch("subprocess.run", return_value=_make_completed_process(returncode=returncode, stderr="error")):
            with pytest.raises(AcpxError):
                close_session(agent, session_name, cwd="/tmp")

    @given(agent=_AGENT_ST, session_name=_SESSION_ST, returncode=st.integers(min_value=1, max_value=255))
    @settings(max_examples=100)
    def test_get_session_status_raises_on_nonzero_exit(self, agent: str, session_name: str, returncode: int) -> None:
        # Feature: acpx-backend-migration, Property 1: AcpxError 错误传播
        with patch("subprocess.run", return_value=_make_completed_process(returncode=returncode, stderr="error")):
            with pytest.raises(AcpxError):
                get_session_status(agent, session_name, cwd="/tmp")


# ---------------------------------------------------------------------------
# 单元测试 1.2：边界条件和具体示例
# ---------------------------------------------------------------------------

class TestAcpxBinaryNotFound:
    """需求 1.6：acpx 二进制不存在时抛出 AcpxError"""

    def test_create_session_raises_when_binary_missing(self) -> None:
        with patch("subprocess.run", side_effect=FileNotFoundError):
            with pytest.raises(AcpxError, match="not found|not installed|not in PATH"):
                create_session("claude", "worker@team", cwd="/tmp")

    def test_send_prompt_raises_when_binary_missing(self) -> None:
        with patch("subprocess.run", side_effect=FileNotFoundError):
            with pytest.raises(AcpxError, match="not found|not installed|not in PATH"):
                send_prompt("claude", "worker@team", "do something", name="worker", team_name="team", cwd="/tmp")

    def test_close_session_raises_when_binary_missing(self) -> None:
        with patch("subprocess.run", side_effect=FileNotFoundError):
            with pytest.raises(AcpxError, match="not found|not installed|not in PATH"):
                close_session("claude", "worker@team", cwd="/tmp")

    def test_get_session_status_raises_when_binary_missing(self) -> None:
        with patch("subprocess.run", side_effect=FileNotFoundError):
            with pytest.raises(AcpxError, match="not found|not installed|not in PATH"):
                get_session_status("claude", "worker@team", cwd="/tmp")


class TestCorrectCommandInvocation:
    """需求 1.1-1.4：各函数在成功时正确调用 subprocess"""

    def test_create_session_calls_correct_command(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process()) as mock_run:
            create_session("claude", "worker@myteam", cwd="/tmp")
            mock_run.assert_called_once()
            cmd = mock_run.call_args[0][0]
            assert cmd == ["acpx", "claude", "sessions", "new", "--name", "worker@myteam"]

    def test_send_prompt_calls_correct_command(self) -> None:
        # status "running" → send_prompt 走 _run 路径（subprocess.run），共两次调用
        with patch("subprocess.run", return_value=_make_completed_process(stdout="running")) as mock_run:
            send_prompt("claude", "worker@myteam", "do the task", name="worker", team_name="myteam", cwd="/tmp")
            assert mock_run.call_count == 2
            cmd = mock_run.call_args_list[-1][0][0]
            assert cmd[0] == "acpx"
            assert "--approve-all" in cmd
            assert "claude" in cmd
            assert "--no-wait" in cmd
            assert "-s" in cmd
            assert "worker@myteam" in cmd

    def test_close_session_calls_correct_command(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process()) as mock_run:
            close_session("claude", "worker@myteam", cwd="/tmp")
            mock_run.assert_called_once()
            cmd = mock_run.call_args[0][0]
            assert cmd == ["acpx", "claude", "sessions", "close", "worker@myteam"]

    def test_get_session_status_calls_correct_command(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process(stdout="alive\n")) as mock_run:
            result = get_session_status("claude", "worker@myteam", cwd="/tmp")
            mock_run.assert_called_once()
            cmd = mock_run.call_args[0][0]
            assert cmd == ["acpx", "claude", "status", "-s", "worker@myteam"]
            assert result == "alive"

    def test_get_session_status_strips_whitespace(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process(stdout="  dead  \n")):
            result = get_session_status("claude", "worker@myteam", cwd="/tmp")
            assert result == "dead"

    def test_custom_acpx_binary_is_used(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process()) as mock_run:
            create_session("claude", "worker@myteam", cwd="/tmp", acpx_binary="/usr/local/bin/acpx")
            cmd = mock_run.call_args[0][0]
            assert cmd[0] == "/usr/local/bin/acpx"


class TestTimeoutParameter:
    """需求 1.7：内部 _run 使用正确的超时值"""

    def test_create_session_uses_default_timeout(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process()) as mock_run:
            create_session("claude", "worker@myteam", cwd="/tmp")
            kwargs = mock_run.call_args[1]
            assert kwargs["timeout"] == 120

    def test_send_prompt_uses_default_timeout(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process(stdout="running")) as mock_run:
            send_prompt("claude", "worker@myteam", "hello", name="worker", team_name="myteam", cwd="/tmp")
            kwargs = mock_run.call_args[1]
            assert kwargs["timeout"] == 120

    def test_close_session_uses_default_timeout(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process()) as mock_run:
            close_session("claude", "worker@myteam", cwd="/tmp")
            kwargs = mock_run.call_args[1]
            assert kwargs["timeout"] == 120

    def test_get_session_status_uses_default_timeout(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process(stdout="alive")) as mock_run:
            get_session_status("claude", "worker@myteam", cwd="/tmp")
            kwargs = mock_run.call_args[1]
            assert kwargs["timeout"] == 120

    def test_default_timeout_is_120(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process()) as mock_run:
            create_session("claude", "worker@myteam", cwd="/tmp")
            kwargs = mock_run.call_args[1]
            assert kwargs["timeout"] == 120


class TestStderrInError:
    """需求 1.5：AcpxError 包含 stderr 内容"""

    def test_error_contains_stderr(self) -> None:
        with patch("subprocess.run", return_value=_make_completed_process(returncode=1, stderr="session not found")):
            with pytest.raises(AcpxError, match="session not found"):
                create_session("claude", "worker@myteam", cwd="/tmp")
