from __future__ import annotations

import json
from argparse import Namespace
from pathlib import Path
import signal
from typing import Any

from vicoa import cli
from vicoa import machine_daemon


def test_ensure_background_daemon_running_skips_when_existing(monkeypatch, tmp_path):
    state_path = tmp_path / "daemon_state.json"
    launched = False

    def fake_popen(*args, **kwargs):
        nonlocal launched
        launched = True
        raise AssertionError("daemon should not start when already running")

    monkeypatch.setattr(machine_daemon, "find_running_daemon_pid", lambda path: 321)
    monkeypatch.setattr(machine_daemon.subprocess, "Popen", fake_popen)

    started = machine_daemon.ensure_background_daemon_running(
        api_key="test-key",
        base_url="https://api.vicoa.ai",
        cwd="/tmp/project",
        state_path=state_path,
    )

    assert started is False
    assert launched is False


def test_ensure_background_daemon_running_launches_and_persists_pid(
    monkeypatch, tmp_path
):
    state_path = tmp_path / "daemon_state.json"
    captured: dict[str, Any] = {}

    class DummyProcess:
        pid = 456

    def fake_popen(command, **kwargs):
        captured["command"] = command
        captured["kwargs"] = kwargs
        return DummyProcess()

    monkeypatch.setattr(machine_daemon, "find_running_daemon_pid", lambda path: None)
    monkeypatch.setattr(machine_daemon.subprocess, "Popen", fake_popen)

    started = machine_daemon.ensure_background_daemon_running(
        api_key="test-key",
        base_url="https://api.vicoa.ai",
        cwd="/tmp/project",
        state_path=state_path,
    )

    assert started is True
    assert captured["command"] == [
        machine_daemon.sys.executable,
        "-m",
        "vicoa.cli",
        "daemon",
        "--api-key",
        "test-key",
        "--base-url",
        "https://api.vicoa.ai",
    ]
    kwargs = captured["kwargs"]
    assert kwargs["cwd"] == "/tmp/project"
    assert kwargs["stdout"] is machine_daemon.subprocess.DEVNULL
    assert kwargs["stderr"] is machine_daemon.subprocess.DEVNULL
    assert kwargs["stdin"] is machine_daemon.subprocess.DEVNULL

    saved_state = json.loads(state_path.read_text())
    assert saved_state["daemon_pid"] == 456


def test_build_daemon_command():
    assert machine_daemon.build_daemon_command(
        api_key="test-key",
        base_url="https://api.vicoa.ai",
    ) == [
        machine_daemon.sys.executable,
        "-m",
        "vicoa.cli",
        "daemon",
        "--api-key",
        "test-key",
        "--base-url",
        "https://api.vicoa.ai",
    ]


def test_maybe_start_machine_daemon_uses_project_path(monkeypatch):
    captured: dict[str, object] = {}

    def fake_ensure_background_daemon_running(**kwargs):
        captured.update(kwargs)
        return True

    monkeypatch.setattr(
        cli,
        "ensure_background_daemon_running",
        fake_ensure_background_daemon_running,
    )

    args = Namespace(
        no_daemon=False,
        base_url="https://api.vicoa.ai",
        project_path="~/workspace/demo",
        cwd=None,
    )

    cli.maybe_start_machine_daemon(args, "test-key")

    assert captured == {
        "api_key": "test-key",
        "base_url": "https://api.vicoa.ai",
        "cwd": str(Path("~/workspace/demo").expanduser().resolve()),
    }


def test_maybe_start_machine_daemon_respects_no_daemon(monkeypatch):
    called = False

    def fake_ensure_background_daemon_running(**kwargs):
        nonlocal called
        called = True
        return True

    monkeypatch.setattr(
        cli,
        "ensure_background_daemon_running",
        fake_ensure_background_daemon_running,
    )

    args = Namespace(
        no_daemon=True,
        base_url="https://api.vicoa.ai",
        project_path=None,
        cwd=None,
    )

    cli.maybe_start_machine_daemon(args, "test-key")

    assert called is False


def test_update_machine_recent_directories_uses_launch_directory(monkeypatch, tmp_path):
    captured: dict[str, object] = {}

    state_path = tmp_path / "daemon_state.json"

    class FakeClient:
        def __init__(self, *, api_key: str, base_url: str):
            captured["api_key"] = api_key
            captured["base_url"] = base_url

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc, tb):
            return False

        def update_machine_recent_directory(
            self,
            machine_id: str,
            cwd: str,
            *,
            cli_version: str | None = None,
            python_version: str | None = None,
        ) -> None:
            captured["machine_id"] = machine_id
            captured["cwd"] = cwd
            captured["cli_version"] = cli_version
            captured["python_version"] = python_version

    monkeypatch.setattr(cli, "STATE_PATH", state_path)
    monkeypatch.setattr(cli, "get_current_version", lambda: "1.2.3")
    monkeypatch.setattr("vicoa.sdk.client.VicoaClient", FakeClient)

    state_path.write_text(json.dumps({"machine_id": "machine-123"}), encoding="utf-8")

    args = Namespace(
        base_url="https://api.vicoa.ai",
        project_path=None,
        cwd="~/workspace/demo",
    )

    cli.update_machine_recent_directories(args, "test-key")

    assert captured == {
        "api_key": "test-key",
        "base_url": "https://api.vicoa.ai",
        "machine_id": "machine-123",
        "cwd": "~/workspace/demo",
        "cli_version": "1.2.3",
        "python_version": cli.platform.python_version(),
    }


def test_update_machine_recent_directories_skips_without_machine_id(
    monkeypatch, tmp_path
):
    state_path = tmp_path / "daemon_state.json"
    state_path.write_text("{}", encoding="utf-8")

    called = False

    class FakeClient:
        def __init__(self, *args, **kwargs):
            nonlocal called
            called = True

    monkeypatch.setattr(cli, "STATE_PATH", state_path)
    monkeypatch.setattr("vicoa.sdk.client.VicoaClient", FakeClient)

    args = Namespace(
        base_url="https://api.vicoa.ai",
        project_path=None,
        cwd="~/workspace/demo",
    )

    cli.update_machine_recent_directories(args, "test-key")

    assert called is False


def test_update_machine_recent_directories_async_starts_background_thread(
    monkeypatch,
):
    captured: dict[str, object] = {}

    class FakeThread:
        def __init__(self, *, target, args, daemon, name):
            captured["target"] = target
            captured["args"] = args
            captured["daemon"] = daemon
            captured["name"] = name

        def start(self) -> None:
            captured["started"] = True

    monkeypatch.setattr(cli.threading, "Thread", FakeThread)

    args = Namespace(
        base_url="https://api.vicoa.ai",
        project_path=None,
        cwd="~/workspace/demo",
    )

    cli.update_machine_recent_directories_async(args, "test-key")

    assert captured == {
        "target": cli.update_machine_recent_directories,
        "args": (args, "test-key"),
        "daemon": True,
        "name": "vicoa-recent-directories-sync",
        "started": True,
    }


def test_stop_background_daemon_when_not_running(tmp_path):
    stopped, message = machine_daemon.stop_background_daemon(
        state_path=tmp_path / "daemon_state.json"
    )

    assert stopped is False
    assert message == "No active Vicoa connection."


def test_stop_background_daemon_clears_saved_pid(monkeypatch, tmp_path):
    state_path = tmp_path / "daemon_state.json"
    state_path.write_text(json.dumps({"daemon_pid": 456}))

    calls: list[tuple[int, int]] = []
    pid_state = {"alive": True}

    monkeypatch.setattr(machine_daemon, "find_running_daemon_pid", lambda path: 456)
    monkeypatch.setattr(
        machine_daemon,
        "_read_process_command",
        lambda pid: "python -m vicoa.cli daemon",
    )

    def fake_kill(pid: int, sig: int) -> None:
        calls.append((pid, sig))
        pid_state["alive"] = False

    monkeypatch.setattr(machine_daemon.os, "kill", fake_kill)
    monkeypatch.setattr(machine_daemon, "_pid_exists", lambda pid: pid_state["alive"])

    stopped, message = machine_daemon.stop_background_daemon(state_path=state_path)

    assert stopped is True
    assert message == "Stopped Vicoa connection."
    assert calls == [(456, signal.SIGTERM)]
    saved_state = json.loads(state_path.read_text())
    assert "daemon_pid" not in saved_state
