"""Integration tests: run mock CLI scripts through the auto-yes PTY proxy.

Each test spawns the full ``auto-yes run`` command-line tool wrapping a mock
script.  The mock script prints realistic Claude/Cursor prompts and validates
that it received the correct auto-responses.  Exit-code 0 means all prompts
were answered correctly.

We allocate a real PTY for each test invocation (via ``pty.openpty()``) so
that auto-yes's ``stdin_is_tty`` check succeeds and it operates in full
interactive mode.

Run:
    pytest tests/scenarios/test_integration.py -v
"""

import contextlib
import errno
import os
import select
import subprocess
import sys
import time

import pytest

SCENARIOS_DIR = os.path.dirname(__file__)
PYTHON = sys.executable

# Unix PTY not available on Windows
pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="PTY not available on Windows")


def _run_autoys_with_pty(mock_script, script_args=None, cli_profiles=None, timeout=30):
    """Run ``auto-yes run`` wrapping *mock_script* with a real PTY.

    Allocates a master/slave PTY pair.  The auto-yes subprocess runs with
    the PTY slave as its controlling terminal (stdin/stdout/stderr).  It
    then creates its own inner PTY for the mock script.  We drain the outer
    master to prevent the process from blocking on output.

    Returns the exit code of the wrapped mock script.
    """
    import pty as _pty

    profiles = cli_profiles or ["claude", "cursor"]
    cli_args = []
    for p in profiles:
        cli_args += ["--cli", p]

    cmd = (
        [PYTHON, "-m", "auto_yes", "run"]
        + cli_args
        + ["--cooldown", "0.1", "--"]
        + [PYTHON, mock_script]
        + (script_args or [])
    )

    master_fd, slave_fd = _pty.openpty()

    try:
        proc = subprocess.Popen(
            cmd,
            stdin=slave_fd,
            stdout=slave_fd,
            stderr=slave_fd,
            close_fds=True,
            preexec_fn=os.setsid,
            env={**os.environ, "TERM": "xterm-256color"},
        )
        # Close slave in parent — child owns it
        os.close(slave_fd)
        slave_fd = -1

        # Drain master to prevent the child from blocking on write
        deadline = time.time() + timeout
        while time.time() < deadline:
            try:
                rlist, _, _ = select.select([master_fd], [], [], 0.05)
                if rlist:
                    try:
                        os.read(master_fd, 4096)
                    except OSError as exc:
                        if exc.errno == errno.EIO:
                            break
                        raise
            except OSError:
                break

            if proc.poll() is not None:
                # Brief drain after exit to flush remaining PTY bytes
                _drain(master_fd, 0.2)
                break

        # Wait for process to fully exit
        try:
            proc.wait(timeout=5)
        except subprocess.TimeoutExpired:
            proc.kill()
            proc.wait()

        return proc.returncode

    finally:
        if slave_fd != -1:
            with contextlib.suppress(OSError):
                os.close(slave_fd)
        with contextlib.suppress(OSError):
            os.close(master_fd)


def _drain(fd, duration):
    """Read and discard remaining bytes from *fd* for up to *duration* seconds."""
    deadline = time.time() + duration
    while time.time() < deadline:
        try:
            rlist, _, _ = select.select([fd], [], [], 0.05)
            if not rlist:
                break
            os.read(fd, 4096)
        except OSError:
            break


# ==================================================================
# Claude integration tests
# ==================================================================


class TestClaudeIntegration:
    """Run mock_claude.py scenarios through the full auto-yes pipeline."""

    mock = os.path.join(SCENARIOS_DIR, "mock_claude.py")

    @pytest.mark.timeout(30)
    def test_trust_folder_scenario(self):
        """auto-yes responds to trust-folder prompt (initial workspace setup)."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "trust_folder"])
        assert rc == 0, "trust_folder scenario failed — prompt was not answered"

    @pytest.mark.timeout(30)
    def test_bash_permission_scenario(self):
        """auto-yes responds to bash command permission dialog."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "bash_permission"])
        assert rc == 0, "bash_permission scenario failed"

    @pytest.mark.timeout(30)
    def test_allow_menu_scenario(self):
        """auto-yes responds to unnumbered Allow once/session/always menu."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "allow_menu"])
        assert rc == 0, "allow_menu scenario failed"

    @pytest.mark.timeout(30)
    def test_allow_numbered_scenario(self):
        """auto-yes responds to numbered allow menu (4 options, newer format)."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "allow_numbered"])
        assert rc == 0, "allow_numbered scenario failed"

    @pytest.mark.timeout(45)
    def test_subagent_chain_scenario(self):
        """auto-yes responds to two chained subagent permission prompts."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "subagent_chain"])
        assert rc == 0, "subagent_chain scenario failed"

    @pytest.mark.timeout(60)
    def test_overnight_loop_scenario(self):
        """auto-yes keeps responding across 3 loop iterations (overnight sim)."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "overnight_loop"])
        assert rc == 0, "overnight_loop scenario failed"


# ==================================================================
# Cursor integration tests
# ==================================================================


class TestCursorIntegration:
    """Run mock_cursor.py scenarios through the full auto-yes pipeline."""

    mock = os.path.join(SCENARIOS_DIR, "mock_cursor.py")

    @pytest.mark.timeout(30)
    def test_trust_workspace_scenario(self):
        """auto-yes responds with 'a' to Cursor workspace trust prompt."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "trust"])
        assert rc == 0, "cursor trust scenario failed"

    @pytest.mark.timeout(30)
    def test_write_file_scenario(self):
        """auto-yes responds to single (y) write-file approval."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "write_file"])
        assert rc == 0, "cursor write_file scenario failed"

    @pytest.mark.timeout(45)
    def test_parallel_ops_scenario(self):
        """auto-yes handles 3 sequential file-operation (y) approvals."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "parallel_ops"])
        assert rc == 0, "cursor parallel_ops scenario failed"

    @pytest.mark.timeout(45)
    def test_subagent_scenario(self):
        """auto-yes handles Cursor subagent: trust prompt + 2 file-op (y) prompts."""
        rc = _run_autoys_with_pty(self.mock, ["--scenario", "subagent"])
        assert rc == 0, "cursor subagent scenario failed"


# ==================================================================
# Parallel agents integration tests
# ==================================================================


class TestParallelAgentsIntegration:
    """Stress-test: multiple forked agents emitting (y) prompts simultaneously."""

    mock = os.path.join(SCENARIOS_DIR, "mock_parallel_agents.py")

    @pytest.mark.timeout(60)
    def test_two_parallel_agents(self):
        """auto-yes handles 2 parallel agents (2 ops each = 4 total approvals)."""
        rc = _run_autoys_with_pty(
            self.mock,
            ["--agents", "2", "--ops-per-agent", "2"],
            cli_profiles=["generic", "cursor"],
        )
        assert rc == 0, "2 parallel agents scenario failed"

    @pytest.mark.timeout(90)
    def test_three_parallel_agents(self):
        """auto-yes handles 3 parallel agents (3 ops each = 9 total approvals)."""
        rc = _run_autoys_with_pty(
            self.mock,
            ["--agents", "3", "--ops-per-agent", "3"],
            cli_profiles=["generic", "cursor"],
        )
        assert rc == 0, "3 parallel agents scenario failed"

    @pytest.mark.timeout(120)
    def test_five_parallel_agents(self):
        """auto-yes handles 5 parallel agents (2 ops each = 10 total approvals)."""
        rc = _run_autoys_with_pty(
            self.mock,
            ["--agents", "5", "--ops-per-agent", "2"],
            cli_profiles=["generic", "cursor"],
        )
        assert rc == 0, "5 parallel agents scenario failed"


# ==================================================================
# Extended overnight loop tests
# ==================================================================


class TestExtendedOvernightLoop:
    """Long-running overnight loop scenarios."""

    mock = os.path.join(SCENARIOS_DIR, "mock_claude.py")

    @pytest.mark.timeout(120)
    def test_long_overnight_loop_10_iterations(self):
        """auto-yes keeps responding across 10 loop iterations (stress overnight sim)."""
        rc = _run_autoys_with_pty(
            self.mock,
            ["--scenario", "long_overnight_loop"],
            cli_profiles=["claude"],
        )
        assert rc == 0, "long_overnight_loop scenario failed"


# ==================================================================
# --auto flag integration tests
# ==================================================================


class TestAutoFlagIntegration:
    """Verify --auto mode: all profiles loaded, very short cooldown, verbose."""

    mock_claude = os.path.join(SCENARIOS_DIR, "mock_claude.py")
    mock_parallel = os.path.join(SCENARIOS_DIR, "mock_parallel_agents.py")

    @pytest.mark.timeout(60)
    def test_auto_flag_claude_scenarios(self):
        """--auto flag correctly handles Claude prompts with near-zero cooldown."""
        mock_claude_path = os.path.join(SCENARIOS_DIR, "mock_claude.py")
        cmd = [
            sys.executable, "-m", "auto_yes", "run", "--auto", "--",
            sys.executable, mock_claude_path,
            "--scenario", "subagent_chain",
        ]
        import pty as _pty

        master_fd, slave_fd = _pty.openpty()
        try:
            proc = subprocess.Popen(
                cmd,
                stdin=slave_fd,
                stdout=slave_fd,
                stderr=slave_fd,
                close_fds=True,
                preexec_fn=os.setsid,
                env={**os.environ, "TERM": "xterm-256color"},
            )
            os.close(slave_fd)
            slave_fd = -1

            deadline = time.time() + 60
            while time.time() < deadline:
                try:
                    rlist, _, _ = select.select([master_fd], [], [], 0.05)
                    if rlist:
                        try:
                            os.read(master_fd, 4096)
                        except OSError as exc:
                            if exc.errno == errno.EIO:
                                break
                            raise
                except OSError:
                    break
                if proc.poll() is not None:
                    _drain(master_fd, 0.2)
                    break

            try:
                proc.wait(timeout=5)
            except subprocess.TimeoutExpired:
                proc.kill()
                proc.wait()

            assert proc.returncode == 0, "--auto flag subagent_chain failed"
        finally:
            if slave_fd != -1:
                with contextlib.suppress(OSError):
                    os.close(slave_fd)
            with contextlib.suppress(OSError):
                os.close(master_fd)

    @pytest.mark.timeout(90)
    def test_auto_flag_parallel_agents(self):
        """--auto flag handles parallel agents scenario (all profiles loaded)."""
        assert _run_autoys_with_pty(
            self.mock_parallel,
            ["--agents", "3", "--ops-per-agent", "2"],
            cli_profiles=None,  # use default (claude + cursor)
            timeout=60,
        ) == 0, "--auto flag parallel agents (via _run_autoys_with_pty) failed"
        # Also test directly with --auto
        cmd = [
            sys.executable, "-m", "auto_yes", "run", "--auto", "--",
            sys.executable, self.mock_parallel,
            "--agents", "2", "--ops-per-agent", "2",
        ]
        import pty as _pty

        master_fd, slave_fd = _pty.openpty()
        try:
            proc = subprocess.Popen(
                cmd,
                stdin=slave_fd,
                stdout=slave_fd,
                stderr=slave_fd,
                close_fds=True,
                preexec_fn=os.setsid,
                env={**os.environ, "TERM": "xterm-256color"},
            )
            os.close(slave_fd)
            slave_fd = -1

            deadline = time.time() + 90
            while time.time() < deadline:
                try:
                    rlist, _, _ = select.select([master_fd], [], [], 0.05)
                    if rlist:
                        try:
                            os.read(master_fd, 4096)
                        except OSError as exc:
                            if exc.errno == errno.EIO:
                                break
                            raise
                except OSError:
                    break
                if proc.poll() is not None:
                    _drain(master_fd, 0.2)
                    break

            try:
                proc.wait(timeout=5)
            except subprocess.TimeoutExpired:
                proc.kill()
                proc.wait()

            assert proc.returncode == 0, "--auto flag parallel agents failed"
        finally:
            if slave_fd != -1:
                with contextlib.suppress(OSError):
                    os.close(slave_fd)
            with contextlib.suppress(OSError):
                os.close(master_fd)
