"""Tests for vr debug command (Pipeline Debugger / Electron launcher)."""

import json
import click
import subprocess
from pathlib import Path
from unittest.mock import patch, Mock

from typer.testing import CliRunner

from vr_cli.cli import app

runner = CliRunner()


# ============================================================================
# debug() command — CLI entry-point tests
# ============================================================================


class TestDebugCommand:
    """Tests for the debug CLI command."""

    def test_debug_outside_project_opens_empty(self, temp_dir, monkeypatch):
        """Should launch debugger with no prefilled info outside a project."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, ["debug"])

        assert result.exit_code == 0
        mock_launch.assert_called_once_with(None, None, None, headless=False, output_path=None, script_path=None)

    def test_debug_outside_project_with_environment(self, temp_dir, monkeypatch):
        """Should pass environment even outside a project."""
        monkeypatch.chdir(temp_dir)

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, ["debug", "-e", "staging"])

        assert result.exit_code == 0
        mock_launch.assert_called_once_with(None, None, "staging", headless=False, output_path=None, script_path=None)

    def test_debug_skip_push_no_lock_file(self, temp_dir, monkeypatch):
        """Should fail when --skip-push used but no agent.lock exists."""
        monkeypatch.chdir(temp_dir)
        voicerun_dir = temp_dir / ".voicerun"
        voicerun_dir.mkdir()

        result = runner.invoke(app, ["debug", "--skip-push"])

        assert result.exit_code != 0
        assert "No agent.lock found" in result.output

    def test_debug_skip_push_with_lock_file(self, voicerun_project_with_lock, monkeypatch):
        """Should use existing deployment when --skip-push is used."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, ["debug", "--skip-push"])

        assert "Using existing deployment" in result.output
        mock_launch.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "debug",
            headless=False, output_path=None, script_path=None,
        )

    def test_debug_push_failure(self, voicerun_project, monkeypatch):
        """Should fail when push fails."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.debug.push_code") as mock_push:
            mock_push.return_value = Mock(success=False, error="Push failed")
            result = runner.invoke(app, ["debug"])

        assert result.exit_code != 0
        assert "Push failed" in result.output

    def test_debug_push_success(self, voicerun_project, monkeypatch):
        """Should push code then launch debugger."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.debug.push_code") as mock_push, \
             patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            mock_push.return_value = Mock(
                success=True,
                agent_id="test-agent",
                function_id="func-123",
            )
            result = runner.invoke(app, ["debug"])

        assert "Pushing code" in result.output
        assert "Pushed func-123" in result.output
        mock_launch.assert_called_once_with("test-agent", "func-123", "debug", headless=False, output_path=None, script_path=None)

    def test_debug_push_no_function_id(self, voicerun_project, monkeypatch):
        """Should warn when no function ID returned from push."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.debug.push_code") as mock_push, \
             patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            mock_push.return_value = Mock(
                success=True,
                agent_id="test-agent",
                function_id=None,
            )
            result = runner.invoke(app, ["debug"])

        assert "no function ID" in result.output
        mock_launch.assert_called_once_with("test-agent", None, "debug", headless=False, output_path=None, script_path=None)

    def test_debug_with_environment(self, voicerun_project_with_lock, monkeypatch):
        """Should pass specified environment to the debugger."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, ["debug", "--skip-push", "-e", "staging"])

        mock_launch.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "staging",
            headless=False, output_path=None, script_path=None,
        )


# ============================================================================
# _launch_debugger() tests
# ============================================================================


class TestLaunchDebugger:
    """Tests for the _launch_debugger helper."""

    def test_no_node(self):
        """Should exit when Node.js is not installed."""
        from vr_cli.commands.debug import _launch_debugger

        with patch("vr_cli.commands.debug.shutil.which", return_value=None):
            import typer
            import pytest
            with pytest.raises((SystemExit, click.exceptions.Exit)):
                _launch_debugger("agent-1", "func-1", "debug")

    def test_no_npx(self):
        """Should exit when npx is not found."""
        from vr_cli.commands.debug import _launch_debugger

        def which_side_effect(cmd):
            if cmd == "node":
                return "/usr/local/bin/node"
            return None  # npx not found

        with patch("vr_cli.commands.debug.shutil.which", side_effect=which_side_effect):
            import pytest
            with pytest.raises((SystemExit, click.exceptions.Exit)):
                _launch_debugger("agent-1", "func-1", "debug")

    def test_npm_install_runs_when_no_node_modules(self, tmp_path):
        """Should run npm install when node_modules doesn't exist."""
        from vr_cli.commands.debug import _launch_debugger

        # Point DEBUGGER_DIR to a temp dir without node_modules
        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "debug")

        # First call = npm install, second = electron launch
        assert mock_run.call_count == 2
        npm_call = mock_run.call_args_list[0]
        assert npm_call[0][0] == ["npm", "install"]
        assert npm_call[1]["cwd"] == tmp_path

    def test_npm_install_skipped_when_node_modules_exist(self, tmp_path):
        """Should skip npm install when node_modules already exists."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "debug")

        # Only the electron launch call, no npm install
        assert mock_run.call_count == 1

    def test_electron_command_includes_all_args(self, tmp_path):
        """Should pass agent-id, api-url, and environment to electron."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "staging")

        cmd = mock_run.call_args[0][0]
        assert cmd[0] == "/usr/local/bin/npx"
        assert "electron" in cmd
        assert "--agent-id" in cmd
        assert "agent-1" in cmd
        assert "--api-url" in cmd
        assert "https://api.example.com" in cmd
        assert "--environment" in cmd
        assert "staging" in cmd
        assert "--function-id" in cmd
        assert "func-1" in cmd

    def test_electron_command_no_args_when_all_none(self, tmp_path):
        """Should omit agent-id, environment, and function-id when all None."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger(None, None, None)

        cmd = mock_run.call_args[0][0]
        assert "--agent-id" not in cmd
        assert "--environment" not in cmd
        assert "--function-id" not in cmd
        assert "--api-url" in cmd
        assert "https://api.example.com" in cmd

    def test_electron_command_omits_function_id_when_none(self, tmp_path):
        """Should omit --function-id when function_id is None."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", None, "debug")

        cmd = mock_run.call_args[0][0]
        assert "--function-id" not in cmd

    def test_npm_install_failure_exits(self, tmp_path):
        """Should exit when npm install fails."""
        from vr_cli.commands.debug import _launch_debugger

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run:
            mock_run.side_effect = subprocess.CalledProcessError(
                1, "npm install", stderr="Some error"
            )

            import pytest
            with pytest.raises((SystemExit, click.exceptions.Exit)):
                _launch_debugger("agent-1", "func-1", "debug")

    def test_electron_failure_exits(self, tmp_path):
        """Should exit when electron process fails."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):
            mock_run.side_effect = subprocess.CalledProcessError(1, "electron")

            import pytest
            with pytest.raises((SystemExit, click.exceptions.Exit)):
                _launch_debugger("agent-1", "func-1", "debug")

    def test_keyboard_interrupt_handled(self, tmp_path):
        """Should handle KeyboardInterrupt gracefully."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):
            mock_run.side_effect = KeyboardInterrupt

            # Should not raise
            _launch_debugger("agent-1", "func-1", "debug")


# ============================================================================
# Headless mode tests
# ============================================================================


class TestHeadlessMode:
    """Tests for --headless mode."""

    def test_debug_headless_outside_project_errors(self, temp_dir, monkeypatch):
        """Headless mode outside a voicerun project should error."""
        monkeypatch.chdir(temp_dir)

        result = runner.invoke(app, ["debug", "--headless"])

        assert result.exit_code != 0
        assert "Headless mode requires a voicerun project" in result.output

    def test_debug_headless_skip_push(self, voicerun_project_with_lock, monkeypatch):
        """Headless + skip-push should pass headless flags to _launch_debugger."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, ["debug", "--headless", "--skip-push"])

        assert result.exit_code == 0
        mock_launch.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "debug",
            headless=True, output_path=None, script_path=None,
        )

    def test_headless_adds_electron_flags(self, tmp_path):
        """--headless and --output-path should appear in the electron command."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "debug", headless=True, output_path="/tmp/out.json")

        cmd = mock_run.call_args[0][0]
        assert "--headless" in cmd
        assert "--output-path" in cmd
        assert "/tmp/out.json" in cmd

    def test_headless_default_output_path(self, tmp_path):
        """Should auto-generate output path when not provided in headless mode."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "debug", headless=True)

        cmd = mock_run.call_args[0][0]
        assert "--headless" in cmd
        assert "--output-path" in cmd
        # Find the output path value
        idx = cmd.index("--output-path")
        output_path = cmd[idx + 1]
        assert "debugger-session-" in output_path
        assert output_path.endswith(".json")

    def test_headless_prints_output_on_success(self, tmp_path):
        """Should print the output file path after a successful headless run."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()
        output_file = tmp_path / "session.json"
        output_file.write_text("[]")

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run"), \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"), \
             patch("builtins.print") as mock_print:

            _launch_debugger("agent-1", "func-1", "debug", headless=True, output_path=str(output_file))

        # Check that the output path was printed
        printed = [str(c) for c in mock_print.call_args_list]
        assert any(str(output_file) in p for p in printed)


# ============================================================================
# Script mode tests
# ============================================================================


class TestScriptMode:
    """Tests for the --script option."""

    def test_script_file_not_found(self, voicerun_project_with_lock, monkeypatch):
        """Should error when script file does not exist."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, ["debug", "--skip-push", "--script", "/nonexistent/script.json"])

        assert result.exit_code != 0
        assert "Script file not found" in result.output

    def test_script_invalid_json(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """Should error on invalid JSON."""
        monkeypatch.chdir(voicerun_project_with_lock)
        bad_file = tmp_path / "bad.json"
        bad_file.write_text("{not valid json")

        result = runner.invoke(app, ["debug", "--skip-push", "--script", str(bad_file)])

        assert result.exit_code != 0
        assert "Invalid JSON" in result.output

    def test_script_not_array(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """Should error when JSON is not an array."""
        monkeypatch.chdir(voicerun_project_with_lock)
        bad_file = tmp_path / "obj.json"
        bad_file.write_text('{"msg": "hello"}')

        result = runner.invoke(app, ["debug", "--skip-push", "--script", str(bad_file)])

        assert result.exit_code != 0
        assert "JSON array" in result.output

    def test_script_not_strings(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """Should error when array contains non-strings."""
        monkeypatch.chdir(voicerun_project_with_lock)
        bad_file = tmp_path / "nums.json"
        bad_file.write_text("[1, 2, 3]")

        result = runner.invoke(app, ["debug", "--skip-push", "--script", str(bad_file)])

        assert result.exit_code != 0
        assert "only strings" in result.output

    def test_script_empty_array(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """Should error when array is empty."""
        monkeypatch.chdir(voicerun_project_with_lock)
        bad_file = tmp_path / "empty.json"
        bad_file.write_text("[]")

        result = runner.invoke(app, ["debug", "--skip-push", "--script", str(bad_file)])

        assert result.exit_code != 0
        assert "must not be empty" in result.output

    def test_script_valid_passes_path(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """Valid script file should pass resolved path to _launch_debugger."""
        monkeypatch.chdir(voicerun_project_with_lock)
        script_file = tmp_path / "script.json"
        script_file.write_text(json.dumps(["Hello", "Goodbye"]))

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, ["debug", "--skip-push", "--script", str(script_file)])

        assert result.exit_code == 0
        mock_launch.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "debug",
            headless=False, output_path=None, script_path=str(script_file.resolve()),
        )

    def test_script_path_in_electron_command(self, tmp_path):
        """--script-path should appear in the electron command."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "debug", script_path="/tmp/script.json")

        cmd = mock_run.call_args[0][0]
        assert "--script-path" in cmd
        assert "/tmp/script.json" in cmd

    def test_script_path_omitted_when_none(self, tmp_path):
        """--script-path should not appear when script_path is None."""
        from vr_cli.commands.debug import _launch_debugger

        (tmp_path / "node_modules").mkdir()

        with patch("vr_cli.commands.debug.DEBUGGER_DIR", tmp_path), \
             patch("vr_cli.commands.debug.shutil.which", return_value="/usr/local/bin/npx"), \
             patch("vr_cli.commands.debug.subprocess.run") as mock_run, \
             patch("vr_cli.commands.debug.API_BASE_URL", "https://api.example.com"):

            _launch_debugger("agent-1", "func-1", "debug")

        cmd = mock_run.call_args[0][0]
        assert "--script-path" not in cmd

    def test_script_with_headless(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """--script + --headless should both pass through."""
        monkeypatch.chdir(voicerun_project_with_lock)
        script_file = tmp_path / "script.json"
        script_file.write_text(json.dumps(["Hello"]))

        with patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, [
                "debug", "--skip-push", "--headless", "--script", str(script_file),
            ])

        assert result.exit_code == 0
        mock_launch.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "debug",
            headless=True, output_path=None, script_path=str(script_file.resolve()),
        )


# ============================================================================
# Outbound mode tests
# ============================================================================


class TestOutboundMode:
    """Tests for the --outbound mode CLI validation and flow."""

    def test_outbound_requires_to_phone_number(self, voicerun_project_with_lock, monkeypatch):
        """Should error when --outbound is used without --to-phone-number."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, ["debug", "--outbound", "--skip-push"])

        assert result.exit_code != 0
        assert "--to-phone-number is required" in result.output

    def test_outbound_incompatible_with_headless(self, voicerun_project_with_lock, monkeypatch):
        """Should error when --outbound is combined with --headless."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, [
            "debug", "--outbound", "--to-phone-number", "+15551234567", "--headless", "--skip-push",
        ])

        assert result.exit_code != 0
        assert "cannot be combined" in result.output

    def test_outbound_incompatible_with_output(self, voicerun_project_with_lock, monkeypatch):
        """Should error when --outbound is combined with --output."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, [
            "debug", "--outbound", "--to-phone-number", "+15551234567", "--output", "/tmp/out.json", "--skip-push",
        ])

        assert result.exit_code != 0
        assert "cannot be combined" in result.output

    def test_outbound_incompatible_with_script(self, voicerun_project_with_lock, monkeypatch, tmp_path):
        """Should error when --outbound is combined with --script."""
        monkeypatch.chdir(voicerun_project_with_lock)
        script_file = tmp_path / "script.json"
        script_file.write_text(json.dumps(["Hello"]))

        result = runner.invoke(app, [
            "debug", "--outbound", "--to-phone-number", "+15551234567", "--script", str(script_file), "--skip-push",
        ])

        assert result.exit_code != 0
        assert "cannot be combined" in result.output

    def test_outbound_outside_project_fails(self, temp_dir, monkeypatch):
        """Should error when --outbound is used outside a voicerun project."""
        monkeypatch.chdir(temp_dir)

        result = runner.invoke(app, [
            "debug", "--outbound", "--to-phone-number", "+15551234567",
        ])

        assert result.exit_code != 0
        assert "requires a voicerun project" in result.output

    def test_to_phone_number_without_outbound_fails(self, voicerun_project_with_lock, monkeypatch):
        """Should error when --to-phone-number is used without --outbound."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, [
            "debug", "--to-phone-number", "+15551234567", "--skip-push",
        ])

        assert result.exit_code != 0
        assert "require --outbound" in result.output

    def test_from_phone_number_without_outbound_fails(self, voicerun_project_with_lock, monkeypatch):
        """Should error when --from-phone-number is used without --outbound."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, [
            "debug", "--from-phone-number", "+15559876543", "--skip-push",
        ])

        assert result.exit_code != 0
        assert "require --outbound" in result.output

    def test_outbound_invalid_to_phone_number(self, voicerun_project_with_lock, monkeypatch):
        """Should error on invalid E.164 format for --to-phone-number."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, [
            "debug", "--outbound", "--to-phone-number", "5551234567", "--skip-push",
        ])

        assert result.exit_code != 0
        assert "E.164" in result.output

    def test_outbound_invalid_from_phone_number(self, voicerun_project_with_lock, monkeypatch):
        """Should error on invalid E.164 format for --from-phone-number."""
        monkeypatch.chdir(voicerun_project_with_lock)

        result = runner.invoke(app, [
            "debug", "--outbound", "--to-phone-number", "+15551234567",
            "--from-phone-number", "bad-number", "--skip-push",
        ])

        assert result.exit_code != 0
        assert "E.164" in result.output

    def test_outbound_skip_push_calls_start(self, voicerun_project_with_lock, monkeypatch):
        """Should call _start_outbound_call with correct args when using --skip-push."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._start_outbound_call") as mock_start:
            result = runner.invoke(app, [
                "debug", "--outbound", "--to-phone-number", "+15551234567", "--skip-push",
            ])

        assert result.exit_code == 0
        mock_start.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "debug",
            "+15551234567", None,
        )

    def test_outbound_push_then_call(self, voicerun_project, monkeypatch):
        """Should push code then start the outbound call."""
        monkeypatch.chdir(voicerun_project)

        with patch("vr_cli.commands.debug.push_code") as mock_push, \
             patch("vr_cli.commands.debug._start_outbound_call") as mock_start:
            mock_push.return_value = Mock(
                success=True, agent_id="agent-abc", function_id="func-123",
            )
            result = runner.invoke(app, [
                "debug", "--outbound", "--to-phone-number", "+15551234567",
            ])

        assert result.exit_code == 0
        mock_push.assert_called_once()
        mock_start.assert_called_once_with(
            "agent-abc", "func-123", "debug", "+15551234567", None,
        )

    def test_outbound_with_from_phone_number(self, voicerun_project_with_lock, monkeypatch):
        """Should pass from_phone_number when provided."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._start_outbound_call") as mock_start:
            result = runner.invoke(app, [
                "debug", "--outbound", "--to-phone-number", "+15551234567",
                "--from-phone-number", "+15559876543", "--skip-push",
            ])

        assert result.exit_code == 0
        mock_start.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "debug",
            "+15551234567", "+15559876543",
        )

    def test_outbound_with_custom_environment(self, voicerun_project_with_lock, monkeypatch):
        """Should use the specified environment."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._start_outbound_call") as mock_start:
            result = runner.invoke(app, [
                "debug", "--outbound", "--to-phone-number", "+15551234567",
                "--skip-push", "-e", "production",
            ])

        assert result.exit_code == 0
        mock_start.assert_called_once_with(
            "test-agent-id-123", "test-function-id-456", "production",
            "+15551234567", None,
        )

    def test_outbound_does_not_launch_debugger(self, voicerun_project_with_lock, monkeypatch):
        """Should NOT launch the Electron debugger in outbound mode."""
        monkeypatch.chdir(voicerun_project_with_lock)

        with patch("vr_cli.commands.debug._start_outbound_call"), \
             patch("vr_cli.commands.debug._launch_debugger") as mock_launch:
            result = runner.invoke(app, [
                "debug", "--outbound", "--to-phone-number", "+15551234567", "--skip-push",
            ])

        assert result.exit_code == 0
        mock_launch.assert_not_called()


# ============================================================================
# _start_outbound_call() tests
# ============================================================================


class TestStartOutboundCall:
    """Tests for the _start_outbound_call helper."""

    def test_success_with_call_id(self):
        """Should print success with telephonyCallId on success."""
        from vr_cli.commands.debug import _start_outbound_call

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": True, "telephonyCallId": "call-123"}

            _start_outbound_call("agent-1", "func-1", "debug", "+15551234567")

        mock_req.assert_called_once()
        call_kwargs = mock_req.call_args
        assert call_kwargs[0][0] == "/v1/agents/agent-1/sessions/start"
        assert call_kwargs[1]["method"] == "POST"

    def test_builds_env_string_with_function_id(self):
        """Should construct environment as 'env|functionId'."""
        from vr_cli.commands.debug import _start_outbound_call

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": True, "telephonyCallId": "call-1"}

            _start_outbound_call("agent-1", "func-abc", "debug", "+15551234567")

        payload = mock_req.call_args[1]["json"]
        assert payload["environment"] == "debug|func-abc"

    def test_builds_env_string_without_function_id(self):
        """Should use just environment name when function_id is None."""
        from vr_cli.commands.debug import _start_outbound_call

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": True, "telephonyCallId": "call-1"}

            _start_outbound_call("agent-1", None, "debug", "+15551234567")

        payload = mock_req.call_args[1]["json"]
        assert payload["environment"] == "debug"

    def test_includes_from_phone_number(self):
        """Should include fromPhoneNumber in inputParameters when provided."""
        from vr_cli.commands.debug import _start_outbound_call

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": True, "telephonyCallId": "call-1"}

            _start_outbound_call("agent-1", "func-1", "debug", "+15551234567", "+15559876543")

        payload = mock_req.call_args[1]["json"]
        assert payload["inputParameters"]["fromPhoneNumber"] == "+15559876543"

    def test_omits_from_phone_number_when_none(self):
        """Should not include fromPhoneNumber when not provided."""
        from vr_cli.commands.debug import _start_outbound_call

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": True, "telephonyCallId": "call-1"}

            _start_outbound_call("agent-1", "func-1", "debug", "+15551234567")

        payload = mock_req.call_args[1]["json"]
        assert "fromPhoneNumber" not in payload["inputParameters"]

    def test_payload_structure(self):
        """Should send correct payload structure matching the API spec."""
        from vr_cli.commands.debug import _start_outbound_call

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": True, "telephonyCallId": "call-1"}

            _start_outbound_call("agent-1", "func-1", "debug", "+15551234567")

        payload = mock_req.call_args[1]["json"]
        assert payload["inputType"] == "phone"
        assert payload["inputParameters"]["toPhoneNumber"] == "+15551234567"
        assert payload["inputParameters"]["phoneNumber"] == "+15551234567"
        assert payload["parameters"] == {}

    def test_api_returns_none_exits(self):
        """Should exit 1 when make_request returns None (HTTP error)."""
        from vr_cli.commands.debug import _start_outbound_call
        import pytest

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = None

            with pytest.raises((SystemExit, click.exceptions.Exit)):
                _start_outbound_call("agent-1", "func-1", "debug", "+15551234567")

    def test_api_returns_no_success_exits(self):
        """Should exit 1 when API returns success=False."""
        from vr_cli.commands.debug import _start_outbound_call
        import pytest

        with patch("vr_cli.commands.debug.make_request") as mock_req:
            mock_req.return_value = {"success": False}

            with pytest.raises((SystemExit, click.exceptions.Exit)):
                _start_outbound_call("agent-1", "func-1", "debug", "+15551234567")
