from __future__ import annotations

import shutil
import subprocess
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

import install


def test_install_creates_idempotent_bin_symlinks_and_copies_icon(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    dest_bin = tmp_path / "bin"
    dest_apps = tmp_path / "applications"
    dest_icons = tmp_path / "icons"
    source_icon = Path(install.__file__).resolve().parent / "icons" / "codex-account-switcher.svg"

    monkeypatch.setattr(install, "shutil", shutil, raising=False)
    monkeypatch.setattr(install.shutil, "which", lambda name: None)

    report = install.install(dest_bin=dest_bin, dest_apps=dest_apps, dest_icons=dest_icons)

    cdx_link = dest_bin / "cdx.py"
    cld_link = dest_bin / "cld.py"
    cld_command = dest_bin / "cld"
    account_lock_link = dest_bin / "account_lock_cli.py"
    account_lock_command = dest_bin / "account-lock"
    claudex_link = dest_bin / "claudex.py"
    claudex_command = dest_bin / "claudex"
    gateway_link = dest_bin / "gateway_account_cli.py"
    gateway_command = dest_bin / "systray-gateway"
    tray_link = dest_bin / "systray_codex_switcher.py"
    statusline_link = dest_bin / "statusline.py"
    icon_target = dest_icons / "codex-account-switcher.svg"
    repo_root = Path(install.__file__).resolve().parent

    assert report.created == (
        account_lock_link,
        cdx_link,
        cld_link,
        claudex_link,
        gateway_link,
        tray_link,
        statusline_link,
        account_lock_command,
        cld_command,
        claudex_command,
        gateway_command,
    )
    assert report.existing == ()
    assert cdx_link.is_symlink()
    assert cld_link.is_symlink()
    assert cld_command.is_symlink()
    assert account_lock_link.is_symlink()
    assert account_lock_command.is_symlink()
    assert claudex_link.is_symlink()
    assert claudex_command.is_symlink()
    assert gateway_link.is_symlink()
    assert gateway_command.is_symlink()
    assert tray_link.is_symlink()
    assert statusline_link.is_symlink()
    assert cdx_link.resolve() == repo_root / "cdx.py"
    assert cld_link.resolve() == repo_root / "cld.py"
    assert cld_command.resolve() == repo_root / "cld.py"
    assert account_lock_link.resolve() == repo_root / "account_lock_cli.py"
    assert account_lock_command.resolve() == repo_root / "account_lock_cli.py"
    assert claudex_link.resolve() == repo_root / "claudex.py"
    assert claudex_command.resolve() == repo_root / "claudex.py"
    assert gateway_link.resolve() == repo_root / "gateway_account_cli.py"
    assert gateway_command.resolve() == repo_root / "gateway_account_cli.py"
    assert statusline_link.resolve() == repo_root / "statusline.py"
    assert tray_link.resolve() == repo_root / "systray_codex_switcher.py"
    assert icon_target.is_file()
    assert icon_target.read_bytes() == source_icon.read_bytes()
    assert not dest_apps.exists()

    second = install.install(dest_bin=dest_bin, dest_apps=dest_apps, dest_icons=dest_icons)

    assert second.created == ()
    assert second.existing == (
        account_lock_link,
        cdx_link,
        cld_link,
        claudex_link,
        gateway_link,
        tray_link,
        statusline_link,
        account_lock_command,
        cld_command,
        claudex_command,
        gateway_command,
    )
    assert cdx_link.is_symlink()
    assert cld_link.is_symlink()
    assert tray_link.is_symlink()
    assert icon_target.read_bytes() == source_icon.read_bytes()


def test_install_replaces_exact_legacy_cld_wrapper(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    dest_bin = tmp_path / "bin"
    dest_bin.mkdir()
    cld = dest_bin / "cld"
    monkeypatch.setattr(install.Path, "home", lambda: tmp_path)
    legacy_source = tmp_path / "Projects" / "overdeck" / "modules" / "systray" / "cld.py"
    cld.write_text(
        f'#!/usr/bin/env bash\nexec python3 {legacy_source} "$@"\n',
        encoding="utf-8",
    )
    cld.chmod(0o755)
    monkeypatch.setattr(install.shutil, "which", lambda name: None)

    report = install.install(
        dest_bin=dest_bin,
        dest_apps=tmp_path / "applications",
        dest_icons=tmp_path / "icons",
    )

    assert cld in report.created
    assert cld.is_symlink()
    assert cld.resolve() == Path(install.__file__).resolve().parent / "cld.py"


def test_install_refuses_foreign_regular_cld_command(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    dest_bin = tmp_path / "bin"
    dest_bin.mkdir()
    cld = dest_bin / "cld"
    cld.write_text("#!/bin/sh\nexit 77\n", encoding="utf-8")
    cld.chmod(0o755)
    monkeypatch.setattr(install.shutil, "which", lambda name: None)

    with pytest.raises(FileExistsError, match="cld"):
        install.install(
            dest_bin=dest_bin,
            dest_apps=tmp_path / "applications",
            dest_icons=tmp_path / "icons",
        )
    assert cld.read_text(encoding="utf-8") == "#!/bin/sh\nexit 77\n"


def test_install_retargets_previous_systray_install(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    previous_root = tmp_path / "previous" / "modules" / "systray"
    previous_root.mkdir(parents=True)
    (previous_root / "deck.module.json").write_text(
        '{"name": "systray"}', encoding="utf-8"
    )
    for filename in (*install._BIN_SCRIPTS, "claudex"):
        (previous_root / ("claudex.py" if filename == "claudex" else filename)).touch()

    dest_bin = tmp_path / "bin"
    dest_bin.mkdir()
    for filename in install._BIN_SCRIPTS:
        (dest_bin / filename).symlink_to(previous_root / filename)
    (dest_bin / "claudex").symlink_to(previous_root / "claudex.py")

    monkeypatch.setattr(install.shutil, "which", lambda name: None)
    install.install(
        dest_bin=dest_bin,
        dest_apps=tmp_path / "applications",
        dest_icons=tmp_path / "icons",
    )

    expected_root = Path(install.__file__).resolve().parent
    assert (dest_bin / "claudex").resolve() == expected_root / "claudex.py"
    assert (dest_bin / "cdx.py").resolve() == expected_root / "cdx.py"


def test_install_rejects_unmanaged_existing_symlink(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    dest_bin = tmp_path / "bin"
    dest_bin.mkdir()
    foreign = tmp_path / "foreign-cdx.py"
    foreign.touch()
    (dest_bin / "cdx.py").symlink_to(foreign)
    monkeypatch.setattr(install.shutil, "which", lambda name: None)

    with pytest.raises(FileExistsError, match="cdx.py"):
        install.install(
            dest_bin=dest_bin,
            dest_apps=tmp_path / "applications",
            dest_icons=tmp_path / "icons",
        )

    assert (dest_bin / "cdx.py").resolve() == foreign


def test_warning_icon_asset_contains_explicit_badge() -> None:
    warning_icon = (
        Path(install.__file__).resolve().parent / "icons" / "codex-account-switcher-warning.svg"
    ).read_text(encoding="utf-8")

    assert 'fill="#dc2626"' in warning_icon
    assert "<circle" in warning_icon
    assert 'fill="#ffffff"' in warning_icon


def test_install_refreshes_icon_cache_when_available(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    dest_bin = tmp_path / "bin"
    dest_apps = tmp_path / "applications"
    dest_icons = tmp_path / "icons"
    calls: list[list[str]] = []

    monkeypatch.setattr(install, "shutil", shutil, raising=False)
    monkeypatch.setattr(
        install.shutil,
        "which",
        lambda name: "/usr/bin/gtk-update-icon-cache" if name == "gtk-update-icon-cache" else None,
    )
    monkeypatch.setattr(install, "subprocess", subprocess, raising=False)

    def fake_run(cmd: list[str], check: bool, stdout, stderr):
        calls.append(cmd)
        return subprocess.CompletedProcess(cmd, 0)

    monkeypatch.setattr(install.subprocess, "run", fake_run)

    install.install(dest_bin=dest_bin, dest_apps=dest_apps, dest_icons=dest_icons)

    icon_target = dest_icons / "codex-account-switcher.svg"
    theme_root = dest_icons.parent.parent.parent

    assert icon_target.is_file()
    assert len(calls) == 1
    assert calls[0][0].endswith("gtk-update-icon-cache")
    assert calls[0][1:] == ["-f", str(theme_root)]


def test_uninstall_removes_only_installed_symlinks(tmp_path: Path) -> None:
    dest_bin = tmp_path / "bin"
    dest_apps = tmp_path / "applications"
    dest_icons = tmp_path / "icons"
    dest_apps.mkdir(parents=True)
    dest_icons.mkdir(parents=True)
    apps_marker = dest_apps / "keep.txt"
    icons_marker = dest_icons / "keep.txt"
    apps_marker.write_text("apps", encoding="utf-8")
    icons_marker.write_text("icons", encoding="utf-8")

    install.install(dest_bin=dest_bin, dest_apps=dest_apps, dest_icons=dest_icons)
    install.uninstall(dest_bin=dest_bin, dest_apps=dest_apps, dest_icons=dest_icons)

    assert not (dest_bin / "cdx.py").exists()
    assert not (dest_bin / "cld.py").exists()
    assert not (dest_bin / "claudex.py").exists()
    assert not (dest_bin / "claudex").exists()
    assert not (dest_bin / "systray_codex_switcher.py").exists()
    assert not (dest_icons / "codex-account-switcher.svg").exists()
    assert apps_marker.read_text(encoding="utf-8") == "apps"
    assert icons_marker.read_text(encoding="utf-8") == "icons"

    install.uninstall(dest_bin=dest_bin, dest_apps=dest_apps, dest_icons=dest_icons)


def test_install_defaults_match_documented_host_paths() -> None:
    home = Path.home()

    assert install.DEFAULT_DEST_BIN == home / ".local" / "bin"
    assert install.DEFAULT_DEST_APPS == home / ".local" / "share" / "applications"
    assert install.DEFAULT_DEST_ICONS == home / ".local" / "share" / "icons" / "hicolor" / "scalable" / "apps"


def test_entrypoint_scripts_are_executable_and_shebangged() -> None:
    repo_root = Path(install.__file__).resolve().parent
    expected_first_line = "#!/usr/bin/env python3"

    for filename in ("cdx.py", "cld.py", "claudex.py", "systray_codex_switcher.py", "statusline.py"):
        path = repo_root / filename
        assert path.read_text(encoding="utf-8").splitlines()[0] == expected_first_line
        assert path.stat().st_mode & 0o111 == 0o111
