import pytest
from typer.testing import CliRunner
from armorer.apps.openclaw import OpenClawInstanceConfig
from armorer.main import app, install
from unittest.mock import patch, MagicMock
from armorer.registry import AppMetadata

runner = CliRunner()

@patch("armorer.main.subprocess.run")
@patch("armorer.installers.dynamic.DynamicInstaller")
@patch("armorer.scanner.VulnerabilityScanner")
@patch("armorer.main.ConfigManager", create=True)
@patch("armorer.main.AppRegistry", create=True)
@patch("armorer.main._ui_confirm")
def test_install_skip_scan(mock_confirm, mock_registry, mock_config, mock_scanner, mock_installer, mock_run):
    """Test that 'armorer install --skip-scan' works without NameError."""
    # Setup mocks
    mock_installer_instance = MagicMock()
    mock_installer.return_value = mock_installer_instance
    mock_installer_instance.install_dir = "/tmp/test"
    mock_installer_instance.app_name = "test-agent"
    mock_installer_instance.instance_name = "test-agent"
    
    # Run command with --skip-scan
    # We use --yes to avoid other interactive prompts
    result = runner.invoke(app, ["install", "https://github.com/user/repo.git", "--skip-scan", "--yes"])
    
    # Verify success
    assert result.exit_code == 0
    assert "installed successfully" in result.stdout
    
    # Verify scanner was NOT called
    mock_scanner.assert_not_called()
    # Verify installer WAS called
    mock_installer.assert_called()

@patch("armorer.main.subprocess.run")
@patch("armorer.installers.dynamic.DynamicInstaller")
@patch("armorer.scanner.VulnerabilityScanner")
@patch("armorer.main.ConfigManager", create=True)
@patch("armorer.main.AppRegistry", create=True)
@patch("armorer.main._ui_confirm")
def test_install_interactive_does_not_prompt_for_scan(mock_confirm, mock_registry, mock_config, mock_scanner, mock_installer, mock_run):
    """Test that install no longer prompts before scanning."""
    # Setup mocks
    mock_installer_instance = MagicMock()
    mock_installer.return_value = mock_installer_instance
    mock_installer_instance.install_dir = "/tmp/test"
    mock_installer_instance.app_name = "test-agent"
    mock_installer_instance.instance_name = "test-agent"

    # Mock scan result: no issues => it won't trigger any follow-up prompt.
    scan_result = MagicMock()
    scan_result.issues = []
    mock_scanner.return_value.scan.return_value = scan_result

    # Prevent any "launch now?" prompt from executing run_callback during the test.
    mock_confirm.return_value = False
    
    # Run command (interactive mode)
    result = runner.invoke(app, ["install", "https://github.com/user/repo.git"])
    
    # Verify success
    assert result.exit_code == 0
    
    # Verify we scanned and the security-scan prompt is never shown.
    mock_scanner.assert_called()
    scanned_msgs = [call.args[0] for call in mock_confirm.call_args_list if call.args]
    assert all("security scan now" not in str(m).lower() for m in scanned_msgs)
    # Verify installer WAS called
    mock_installer.assert_called()


def test_uninstall_uses_docker_runner_compose_down(monkeypatch, tmp_path):
    from armorer.cli import install as cli_install

    class _App:
        name = "fake"
        agent_id = "fake"
        install_path = str(tmp_path)

    class _Registry:
        def remove_app(self, name: str) -> None:
            return None

    monkeypatch.setattr(cli_install, "AppRegistry", lambda: _Registry())
    monkeypatch.setattr(cli_install.cli_registry, "get_app_with_openclaw_fallback", lambda *a, **k: _App())
    monkeypatch.setattr(cli_install.cli_registry, "resolve_install_path", lambda p: tmp_path)
    # uninstall_impl imports questionary inside the function; patch the module import.
    import types, sys
    sys.modules["questionary"] = types.SimpleNamespace(confirm=lambda *a, **k: type("X", (), {"ask": lambda self: True})())

    called = {}

    def fake_compose_down(*, cwd, volumes=False, **kwargs):
        called["cwd"] = cwd
        called["volumes"] = volumes
        return cli_install.docker_runner.DockerResult(args=["docker", "compose", "down"], returncode=0, stdout="", stderr="")

    monkeypatch.setattr(cli_install.docker_runner, "compose_down", fake_compose_down)

    # --force avoids aborting on compose errors
    cli_install.uninstall_impl("fake", force=True)
    assert called["cwd"] == tmp_path
    assert called["volumes"] is True


@patch("armorer.main.load_dotenv")
@patch("armorer.main._print_logo")
@patch("armorer.main._check_notifications")
@patch("armorer.main._instances_for_agent")
@patch("armorer.main._run_armorer_managed_openclaw_setup")
@patch("armorer.main._get_app_with_openclaw_fallback")
@patch("armorer.main.AppRegistry", create=True)
@patch("armorer.main.sys.stdin")
@patch("armorer.main._infer_agent_id")
@patch("armorer.installers.dynamic.DynamicInstaller")
@patch("armorer.main.ConfigManager", create=True)
def test_install_openclaw_runs_setup_immediately_after_install(
    mock_config,
    mock_installer,
    mock_infer_agent_id,
    mock_stdin,
    mock_registry_cls,
    mock_get_app,
    mock_managed_setup,
    mock_instances_for_agent,
    mock_check_notifications,
    mock_print_logo,
    mock_load_dotenv,
    tmp_path,
):
    install_dir = tmp_path / "openclaw"
    install_dir.mkdir()
    mock_installer_instance = MagicMock()
    mock_installer.return_value = mock_installer_instance
    mock_installer_instance.instance_name = "openclaw"
    mock_installer_instance.install.return_value = None
    mock_infer_agent_id.return_value = "openclaw"
    mock_stdin.isatty.return_value = True
    mock_instances_for_agent.return_value = []
    mock_managed_setup.return_value = 0

    app_meta = MagicMock()
    app_meta.name = "openclaw"
    app_meta.install_path = str(install_dir)
    app_meta.agent_id = "openclaw"
    app_meta.run_commands = [{"command": "setup", "category": "setup"}]
    mock_get_app.return_value = app_meta

    install(MagicMock(args=[]), "openclaw", yes=False, skip_scan=True, alias=None, use=None)

    mock_managed_setup.assert_called_once_with("openclaw")

@patch("armorer.main._ensure_runtime_security_approvals")
@patch("armorer.main.AppRegistry", create=True)
@patch("armorer.main.ConfigManager", create=True)
@patch("armorer.docker_runner.compose_up")
def test_run_noninteractive_defaults_to_background(mock_compose_up, mock_config, mock_registry, mock_approvals, tmp_path):
    install_dir = tmp_path / "installed" / "openclaw"
    install_dir.mkdir(parents=True)
    (install_dir / "docker-compose.yml").write_text(
        "services:\n  app:\n    image: alpine:3.20\n"
    )

    app_meta = AppMetadata(
        name="openclaw",
        agent_id="openclaw",
        install_path=str(install_dir),
    )
    mock_registry.return_value.get_app.return_value = app_meta
    mock_compose_up.return_value = MagicMock(returncode=0, stdout="", stderr="")

    result = runner.invoke(app, ["run", "openclaw"])

    assert result.exit_code == 0
    assert "Non-interactive run detected; starting openclaw in background mode." in result.stdout
    assert "active in background" in result.stdout
    assert mock_compose_up.call_args.kwargs["cwd"] == install_dir
    assert mock_compose_up.call_args.kwargs["timeout"] == 120
