"""Tests for the LLMHosts CLI (Click commands)."""

from __future__ import annotations

from typing import TYPE_CHECKING

import pytest
from click.testing import CliRunner

from llmhosts import __version__
from llmhosts.cli import main

if TYPE_CHECKING:
    from pathlib import Path


@pytest.fixture()
def runner() -> CliRunner:
    return CliRunner()


# -------------------------------------------------------------------
# --version
# -------------------------------------------------------------------


@pytest.mark.unit
def test_version_flag(runner: CliRunner) -> None:
    result = runner.invoke(main, ["--version"])
    assert result.exit_code == 0
    assert __version__ in result.output


# -------------------------------------------------------------------
# --help
# -------------------------------------------------------------------


@pytest.mark.unit
def test_help_flag(runner: CliRunner) -> None:
    result = runner.invoke(main, ["--help"])
    assert result.exit_code == 0
    assert "LLMHosts" in result.output
    assert "serve" in result.output


@pytest.mark.unit
def test_serve_help(runner: CliRunner) -> None:
    result = runner.invoke(main, ["serve", "--help"])
    assert result.exit_code == 0
    assert "--host" in result.output
    assert "--port" in result.output


@pytest.mark.unit
def test_keys_help(runner: CliRunner) -> None:
    result = runner.invoke(main, ["keys", "--help"])
    assert result.exit_code == 0
    assert "add" in result.output
    assert "list" in result.output
    assert "remove" in result.output


# -------------------------------------------------------------------
# doctor
# -------------------------------------------------------------------


@pytest.mark.unit
def test_doctor_runs_without_crash(runner: CliRunner) -> None:
    """Doctor command should complete (even if checks fail) without crashing."""
    result = runner.invoke(main, ["doctor"])
    # exit_code 0 is ideal, but doctor may return 1 if checks fail
    # The key is it doesn't crash with an unhandled exception
    assert result.exit_code in (0, 1)
    assert result.exception is None or isinstance(result.exception, SystemExit)


# -------------------------------------------------------------------
# No subcommand shows help
# -------------------------------------------------------------------


@pytest.mark.unit
def test_no_subcommand_shows_help(runner: CliRunner) -> None:
    result = runner.invoke(main, [])
    assert result.exit_code == 0
    assert "LLMHosts" in result.output


# -------------------------------------------------------------------
# keys list (empty)
# -------------------------------------------------------------------


@pytest.mark.unit
def test_keys_list_empty(runner: CliRunner) -> None:
    """Keys list with no stored keys should succeed."""
    result = runner.invoke(main, ["keys", "list"])
    # Should succeed even with no keys
    assert result.exit_code == 0


# -------------------------------------------------------------------
# engine commands
# -------------------------------------------------------------------


@pytest.mark.unit
def test_engine_help(runner: CliRunner) -> None:
    result = runner.invoke(main, ["engine", "--help"])
    assert result.exit_code == 0
    assert "list" in result.output
    assert "set" in result.output
    assert "compare" in result.output


@pytest.mark.unit
def test_engine_list(runner: CliRunner) -> None:
    """Engine list should show Ollama and vLLM engines."""
    result = runner.invoke(main, ["engine", "list"])
    assert result.exit_code == 0
    assert "ollama" in result.output
    assert "vllm" in result.output


@pytest.mark.unit
def test_engine_compare(runner: CliRunner) -> None:
    """Engine compare should complete (even when engines unavailable)."""
    result = runner.invoke(main, ["engine", "compare", "llama3.2"])
    assert result.exit_code == 0
    assert "Local Engine" in result.output
    assert "vLLM" in result.output


# -------------------------------------------------------------------
# config commands
# -------------------------------------------------------------------


@pytest.mark.unit
def test_config_help(runner: CliRunner) -> None:
    result = runner.invoke(main, ["config", "--help"])
    assert result.exit_code == 0
    assert "set" in result.output


@pytest.mark.unit
def test_hive_help(runner: CliRunner) -> None:
    result = runner.invoke(main, ["hive", "--help"])
    assert result.exit_code == 0
    assert "create" in result.output
    assert "join" in result.output
    assert "karma" in result.output
    assert "members" in result.output


@pytest.mark.unit
def test_hive_create(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Hive create should create a Hive and show invite code."""
    llmhosts_dir = tmp_path / ".llmhosts"
    llmhosts_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr("llmhosts.config.llmhosts_dir", lambda: llmhosts_dir)

    result = runner.invoke(main, ["hive", "create", "Test Hive", "--owner-name", "Alice"])
    assert result.exit_code == 0
    assert "Created Hive" in result.output
    assert "Test Hive" in result.output
    assert "Invite:" in result.output


@pytest.mark.unit
def test_hive_share_help(runner: CliRunner) -> None:
    """hive share should show --on, --off, --schedule, --model."""
    result = runner.invoke(main, ["hive", "share", "--help"])
    assert result.exit_code == 0
    assert "--on" in result.output
    assert "--off" in result.output
    assert "schedule" in result.output
    assert "model" in result.output


@pytest.mark.unit
def test_hive_share_without_hive(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """hive share without a Hive should exit with error."""
    llmhosts_dir = tmp_path / ".llmhosts"
    llmhosts_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr("llmhosts.config.llmhosts_dir", lambda: llmhosts_dir)

    result = runner.invoke(main, ["hive", "share", "--on"])
    assert result.exit_code == 1
    assert "Not in a Hive" in result.output


@pytest.mark.unit
def test_hive_share_on_off(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """hive share --on and --off should update and show settings."""
    llmhosts_dir = tmp_path / ".llmhosts"
    llmhosts_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.setattr("llmhosts.config.llmhosts_dir", lambda: llmhosts_dir)

    create = runner.invoke(main, ["hive", "create", "Share Hive", "--owner-name", "Alice"])
    assert create.exit_code == 0

    off = runner.invoke(main, ["hive", "share", "--off"])
    assert off.exit_code == 0
    assert "Sharing updated" in off.output
    assert "Enabled" in off.output

    on = runner.invoke(main, ["hive", "share", "--on"])
    assert on.exit_code == 0
    assert "Sharing updated" in on.output


@pytest.mark.unit
def test_config_set_engine(runner: CliRunner, tmp_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
    """Config set engine vllm should persist to config file."""
    from pathlib import Path

    llmhosts_dir = Path(tmp_path) / ".llmhosts"
    llmhosts_dir.mkdir(parents=True, exist_ok=True)
    cfg_path = llmhosts_dir / "config.toml"
    cfg_path.write_text('[ollama]\nhost = "http://localhost:11434"\n[engine]\ntype = "ollama"')

    monkeypatch.setenv("HOME", str(tmp_path))
    # Path.home() uses os.environ on Unix
    result = runner.invoke(main, ["config", "set", "engine", "vllm"])
    assert result.exit_code == 0
    assert "vllm" in result.output or "✓" in result.output
    assert cfg_path.exists()
    content = cfg_path.read_text()
    assert "vllm" in content


# -------------------------------------------------------------------
# uninstall
# -------------------------------------------------------------------


@pytest.mark.unit
def test_uninstall_help(runner: CliRunner) -> None:
    result = runner.invoke(main, ["uninstall", "--help"])
    assert result.exit_code == 0
    assert "--keep-data" in result.output
    assert "--remove-engine" in result.output
    assert "--yes" in result.output


@pytest.mark.unit
def test_uninstall_shows_plan_and_confirms(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Uninstall should show what will be removed and ask for confirmation."""
    from pathlib import Path as _Path

    data_dir = _Path(tmp_path) / ".llmhosts"
    data_dir.mkdir()
    (data_dir / "config.toml").write_text("test = true")
    (data_dir / "keys.db").write_bytes(b"\x00" * 1024)

    monkeypatch.setenv("HOME", str(tmp_path))
    # Say "no" to confirmation
    result = runner.invoke(main, ["uninstall"], input="n\n")
    assert result.exit_code == 0
    assert "will be removed" in result.output or "Nothing to remove" in result.output
    # Data dir should still exist (we said no)
    assert data_dir.exists()


@pytest.mark.unit
def test_uninstall_removes_data_dir(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Uninstall -y should remove the data directory."""
    from pathlib import Path as _Path

    data_dir = _Path(tmp_path) / ".llmhosts"
    data_dir.mkdir()
    (data_dir / "config.toml").write_text("test = true")

    monkeypatch.setenv("HOME", str(tmp_path))
    # Mock uv away so it doesn't try real uninstall
    monkeypatch.setattr("shutil.which", lambda cmd: None)

    result = runner.invoke(main, ["uninstall", "-y"])
    assert result.exit_code == 0
    assert "Removed" in result.output or "uninstalled" in result.output.lower()
    assert not data_dir.exists()


@pytest.mark.unit
def test_uninstall_keep_data(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Uninstall --keep-data should preserve the data directory."""
    from pathlib import Path as _Path

    data_dir = _Path(tmp_path) / ".llmhosts"
    data_dir.mkdir()
    (data_dir / "config.toml").write_text("test = true")

    monkeypatch.setenv("HOME", str(tmp_path))
    monkeypatch.setattr("shutil.which", lambda cmd: None)

    result = runner.invoke(main, ["uninstall", "--keep-data", "-y"])
    assert result.exit_code == 0
    # Data directory should still be there
    assert data_dir.exists()


@pytest.mark.unit
def test_uninstall_nothing_to_remove(runner: CliRunner, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Uninstall on a clean system should say nothing to remove."""
    monkeypatch.setenv("HOME", str(tmp_path))
    monkeypatch.setattr("shutil.which", lambda cmd: None)

    result = runner.invoke(main, ["uninstall", "-y"])
    assert result.exit_code == 0
    assert "Nothing to remove" in result.output
