from pathlib import Path

import pytest
from typer.testing import CliRunner

from a2a_agent import autostart
from a2a_agent import cli
from a2a_agent.config import AgentConfig
from a2a_agent.config import load_config
from a2a_agent.config import save_config


runner = CliRunner()


@pytest.mark.parametrize("role", ["worker", "commander"])
def test_set_executor_cli_saves_normalized_value_for_any_config_role(
    monkeypatch,
    tmp_path: Path,
    role: str,
) -> None:
    home = tmp_path / "a2a-home"
    config_path = home / "config.yaml"
    monkeypatch.setenv("A2A_HOME", str(home))
    save_config(AgentConfig(role=role, executor="echo"), config_path)

    result = runner.invoke(cli.app, ["set-executor", "OpenCode"])

    assert result.exit_code == 0
    assert "saved executor: opencode" in result.output
    assert f"saved config: {config_path}" in result.output
    saved = load_config(config_path)
    assert saved.role == role
    assert saved.executor == "opencode"


@pytest.mark.parametrize("role", ["worker", "commander"])
def test_set_cursor_key_cli_saves_key_without_echoing_it(
    monkeypatch,
    tmp_path: Path,
    role: str,
) -> None:
    home = tmp_path / "a2a-home"
    config_path = home / "config.yaml"
    cursor_api_key = "test-cursor-api-key-456"
    monkeypatch.setenv("A2A_HOME", str(home))
    save_config(AgentConfig(role=role), config_path)

    result = runner.invoke(cli.app, ["set-cursor-key", cursor_api_key])

    assert result.exit_code == 0
    assert result.output == f"saved config: {config_path}\n"
    assert cursor_api_key not in result.output
    assert load_config(config_path).cursor_api_key == cursor_api_key


def test_set_cursor_key_cli_rejects_whitespace_without_echoing_value(
    monkeypatch,
    tmp_path: Path,
) -> None:
    home = tmp_path / "a2a-home"
    config_path = home / "config.yaml"
    invalid_key = "test cursor key"
    monkeypatch.setenv("A2A_HOME", str(home))
    save_config(AgentConfig(), config_path)

    result = runner.invoke(cli.app, ["set-cursor-key", invalid_key])

    assert result.exit_code == 2
    assert invalid_key not in result.output
    assert load_config(config_path).cursor_api_key is None


@pytest.mark.parametrize("role", ["worker", "commander"])
def test_set_ollama_key_cli_saves_key_without_echoing_it(
    monkeypatch,
    tmp_path: Path,
    role: str,
) -> None:
    home = tmp_path / "a2a-home"
    config_path = home / "config.yaml"
    ollama_api_key = "test-ollama-api-key-456"
    monkeypatch.setenv("A2A_HOME", str(home))
    save_config(AgentConfig(role=role), config_path)

    result = runner.invoke(cli.app, ["set-ollama-key", ollama_api_key])

    assert result.exit_code == 0
    assert result.output == f"saved config: {config_path}\n"
    assert ollama_api_key not in result.output
    saved = load_config(config_path)
    assert saved.role == role
    assert saved.ollama_api_key == ollama_api_key


def test_set_ollama_key_cli_rejects_whitespace_without_echoing_value(
    monkeypatch,
    tmp_path: Path,
) -> None:
    home = tmp_path / "a2a-home"
    config_path = home / "config.yaml"
    invalid_key = "test ollama api key"
    monkeypatch.setenv("A2A_HOME", str(home))
    save_config(AgentConfig(), config_path)

    result = runner.invoke(cli.app, ["set-ollama-key", invalid_key])

    assert result.exit_code == 2
    assert invalid_key not in result.output
    assert load_config(config_path).ollama_api_key is None


@pytest.mark.parametrize("role", ["worker", "commander"])
def test_set_ollama_model_cli_saves_model_without_echoing_it(
    monkeypatch,
    tmp_path: Path,
    role: str,
) -> None:
    home = tmp_path / "a2a-home"
    config_path = home / "config.yaml"
    ollama_model = "test-ollama-model:42b"
    monkeypatch.setenv("A2A_HOME", str(home))
    save_config(AgentConfig(role=role), config_path)

    result = runner.invoke(cli.app, ["set-ollama-model", ollama_model])

    assert result.exit_code == 0
    assert result.output == f"saved config: {config_path}\n"
    assert ollama_model not in result.output
    saved = load_config(config_path)
    assert saved.role == role
    assert saved.ollama_model == ollama_model


def test_control_cli_sends_control_task_and_prints_response(monkeypatch) -> None:
    observed: dict[str, str] = {}

    def fake_send_task(
        device_name: str,
        text: str,
        *,
        control: str,
    ) -> cli.SendResult:
        observed.update(
            device_name=device_name,
            text=text,
            control=control,
        )
        return cli.SendResult(
            task_id="task-control",
            status="completed",
            text="control completed: install-autostart",
        )

    monkeypatch.setattr(cli, "send_task", fake_send_task)

    result = runner.invoke(
        cli.app,
        ["control", "데스크탑 PC", "install-autostart"],
    )

    assert result.exit_code == 0
    assert observed == {
        "device_name": "데스크탑 PC",
        "text": "",
        "control": "install-autostart",
    }
    assert "task: task-control" in result.output
    assert "status: completed" in result.output
    assert "control completed: install-autostart" in result.output


def test_control_cli_forwards_parameterized_control_unchanged(monkeypatch) -> None:
    observed: dict[str, str] = {}

    def fake_send_task(
        device_name: str,
        text: str,
        *,
        control: str,
    ) -> cli.SendResult:
        observed.update(
            device_name=device_name,
            text=text,
            control=control,
        )
        return cli.SendResult(
            task_id="task-set-executor",
            status="completed",
            text="executor set to opencode; applies after next worker restart",
        )

    monkeypatch.setattr(cli, "send_task", fake_send_task)

    result = runner.invoke(
        cli.app,
        ["control", "windows-pc", "set-executor:opencode"],
    )

    assert result.exit_code == 0
    assert observed == {
        "device_name": "windows-pc",
        "text": "",
        "control": "set-executor:opencode",
    }
    assert "executor set to opencode" in result.output


def test_control_cli_reports_timeout(monkeypatch) -> None:
    def time_out(*args, **kwargs):
        raise TimeoutError("worker did not respond")

    monkeypatch.setattr(cli, "send_task", time_out)

    result = runner.invoke(
        cli.app,
        ["control", "worker-a", "uninstall-autostart"],
    )

    assert result.exit_code == 1
    assert "control timed out: worker did not respond" in result.output


def test_control_cli_returns_failure_when_worker_rejects_control(monkeypatch) -> None:
    monkeypatch.setattr(
        cli,
        "send_task",
        lambda *args, **kwargs: cli.SendResult(
            task_id="task-rejected",
            status="failed",
            text="unsupported control: run-shell",
        ),
    )

    result = runner.invoke(
        cli.app,
        ["control", "worker-a", "run-shell"],
    )

    assert result.exit_code == 1
    assert "status: failed" in result.output
    assert "unsupported control: run-shell" in result.output


def test_install_autostart_cli_uses_selected_executor_and_prints_safety_summary(
    monkeypatch,
    tmp_path: Path,
) -> None:
    observed: dict[str, str] = {}
    registered_path = tmp_path / "start-worker.cmd"
    backup_path = tmp_path / "start-worker.cmd.bak"

    def fake_install(executor: str) -> autostart.AutostartResult:
        observed["executor"] = executor
        return autostart.AutostartResult(
            action="installed",
            platform="windows",
            path=registered_path,
            enabled=True,
            backup_path=backup_path,
        )

    monkeypatch.setattr(cli, "load_config", lambda: AgentConfig(executor="echo"))
    monkeypatch.setenv("A2A_EXECUTOR", "cursor")
    monkeypatch.setattr(cli.autostart, "install_autostart", fake_install)

    result = runner.invoke(cli.app, ["install-autostart"])

    assert result.exit_code == 0
    assert observed["executor"] == "cursor"
    assert f"registered path: {registered_path}" in result.output
    assert "executor: cursor" in result.output
    assert f"backup: {backup_path}" in result.output
    assert "effective: next login or boot" in result.output
    assert "current worker: not started" in result.output


def test_install_autostart_cli_prints_linux_manual_enable(monkeypatch, tmp_path) -> None:
    service_path = tmp_path / "a2a-worker.service"
    manual_command = "systemctl --user enable a2a-worker.service"
    monkeypatch.delenv("A2A_EXECUTOR", raising=False)
    monkeypatch.setattr(
        cli,
        "load_config",
        lambda: AgentConfig(executor="opencode"),
    )
    monkeypatch.setattr(
        cli.autostart,
        "install_autostart",
        lambda executor: autostart.AutostartResult(
            action="installed",
            platform="linux",
            path=service_path,
            enabled=False,
            manual_command=manual_command,
        ),
    )

    result = runner.invoke(cli.app, ["install-autostart"])

    assert result.exit_code == 0
    assert f"manual enable required: {manual_command}" in result.output
    assert "current worker: not started" in result.output


def test_uninstall_autostart_cli_does_not_claim_running_worker_stopped(
    monkeypatch,
    tmp_path: Path,
) -> None:
    registered_path = tmp_path / "worker.plist"
    monkeypatch.setattr(
        cli.autostart,
        "uninstall_autostart",
        lambda: autostart.AutostartResult(
            action="uninstalled",
            platform="macos",
            path=registered_path,
            enabled=False,
        ),
    )

    result = runner.invoke(cli.app, ["uninstall-autostart"])

    assert result.exit_code == 0
    assert f"removed path: {registered_path}" in result.output
    assert "current worker: not stopped" in result.output


def test_uninstall_autostart_cli_reports_incomplete_manual_removal(
    monkeypatch,
    tmp_path: Path,
) -> None:
    registered_path = tmp_path / "a2a-worker.service"
    manual_command = "systemctl --user disable a2a-worker.service"
    monkeypatch.setattr(
        cli.autostart,
        "uninstall_autostart",
        lambda: autostart.AutostartResult(
            action="uninstalled",
            platform="linux",
            path=registered_path,
            enabled=True,
            manual_command=manual_command,
        ),
    )

    result = runner.invoke(cli.app, ["uninstall-autostart"])

    assert result.exit_code == 0
    assert f"registration removal incomplete: {registered_path}" in result.output
    assert f"manual disable required: {manual_command}" in result.output
    assert "removed path:" not in result.output


def test_install_autostart_cli_reports_failure(monkeypatch) -> None:
    monkeypatch.setattr(cli, "load_config", lambda: AgentConfig())

    def fail_install(executor: str) -> autostart.AutostartResult:
        raise autostart.AutostartError("unsupported operating system")

    monkeypatch.setattr(cli.autostart, "install_autostart", fail_install)

    result = runner.invoke(cli.app, ["install-autostart"])

    assert result.exit_code == 1
    assert "autostart install failed: unsupported operating system" in result.output


def test_install_autostart_cli_rejects_commander_config(monkeypatch) -> None:
    monkeypatch.setattr(
        cli,
        "load_config",
        lambda: AgentConfig(role="commander"),
    )

    result = runner.invoke(cli.app, ["install-autostart"])

    assert result.exit_code == 1
    assert "autostart install failed: worker config required" in result.output
