"""Tests for the periodic update checker."""

from __future__ import annotations

import json
from datetime import datetime, timedelta, timezone
from unittest.mock import MagicMock, patch

import httpx

from cli.config import CACHE_DIR_ENV
from cli.update_checker import (
    CACHE_FILENAME,
    DEFAULT_TTL_SECONDS,
    UPDATE_CHECK_ENV,
    UPDATE_CHECK_INTERVAL_ENV,
    _compare_versions,
    _fetch_latest_version_with_timeout,
    _get_ttl_seconds,
    _is_cache_stale,
    _is_update_check_disabled,
    _read_cache,
    _write_cache,
    detect_install_method,
    format_update_notification,
    get_upgrade_command,
    run_startup_update_check,
)


class TestIsUpdateCheckDisabled:
    def test_disabled_with_zero(self, monkeypatch):
        monkeypatch.setenv(UPDATE_CHECK_ENV, "0")
        assert _is_update_check_disabled() is True

    def test_disabled_with_false(self, monkeypatch):
        monkeypatch.setenv(UPDATE_CHECK_ENV, "false")
        assert _is_update_check_disabled() is True

    def test_disabled_with_no(self, monkeypatch):
        monkeypatch.setenv(UPDATE_CHECK_ENV, "no")
        assert _is_update_check_disabled() is True

    def test_enabled_by_default(self, monkeypatch):
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)
        assert _is_update_check_disabled() is False

    def test_enabled_with_one(self, monkeypatch):
        monkeypatch.setenv(UPDATE_CHECK_ENV, "1")
        assert _is_update_check_disabled() is False


class TestGetTtlSeconds:
    def test_default_ttl(self, monkeypatch):
        monkeypatch.delenv(UPDATE_CHECK_INTERVAL_ENV, raising=False)
        assert _get_ttl_seconds() == DEFAULT_TTL_SECONDS

    def test_custom_ttl(self, monkeypatch):
        monkeypatch.setenv(UPDATE_CHECK_INTERVAL_ENV, "3600")
        assert _get_ttl_seconds() == 3600

    def test_invalid_ttl_falls_back_to_default(self, monkeypatch):
        monkeypatch.setenv(UPDATE_CHECK_INTERVAL_ENV, "invalid")
        assert _get_ttl_seconds() == DEFAULT_TTL_SECONDS


class TestCacheOperations:
    def test_read_cache_missing_file(self, tmp_path, monkeypatch):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        assert _read_cache() is None

    def test_read_cache_valid(self, tmp_path, monkeypatch):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        cache_file = tmp_path / CACHE_FILENAME
        data = {
            "last_check": "2024-01-21T12:00:00+00:00",
            "latest_version": "2.0.0",
            "installed_version": "1.0.0",
        }
        cache_file.write_text(json.dumps(data))

        result = _read_cache()
        assert result == data

    def test_read_cache_invalid_json(self, tmp_path, monkeypatch):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text("not json")

        assert _read_cache() is None

    def test_read_cache_missing_fields(self, tmp_path, monkeypatch):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps({"only_one_field": "value"}))

        assert _read_cache() is None

    def test_write_cache_creates_file(self, tmp_path, monkeypatch):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        _write_cache("2.0.0", "1.0.0")

        cache_file = tmp_path / CACHE_FILENAME
        assert cache_file.exists()

        data = json.loads(cache_file.read_text())
        assert data["latest_version"] == "2.0.0"
        assert data["installed_version"] == "1.0.0"
        assert "last_check" in data


class TestIsCacheStale:
    def test_stale_cache(self):
        old_time = datetime.now(timezone.utc) - timedelta(hours=25)
        cache = {"last_check": old_time.isoformat()}
        assert _is_cache_stale(cache, DEFAULT_TTL_SECONDS) is True

    def test_fresh_cache(self):
        recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
        cache = {"last_check": recent_time.isoformat()}
        assert _is_cache_stale(cache, DEFAULT_TTL_SECONDS) is False

    def test_invalid_timestamp(self):
        cache = {"last_check": "not a timestamp"}
        assert _is_cache_stale(cache, DEFAULT_TTL_SECONDS) is True

    def test_missing_timestamp(self):
        cache = {}
        assert _is_cache_stale(cache, DEFAULT_TTL_SECONDS) is True

    def test_z_suffix_timestamp(self):
        recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
        # Use Z suffix format
        cache = {"last_check": recent_time.strftime("%Y-%m-%dT%H:%M:%S") + "Z"}
        assert _is_cache_stale(cache, DEFAULT_TTL_SECONDS) is False


class TestFetchLatestVersion:
    def test_successful_fetch(self, monkeypatch):
        mock_response = MagicMock()
        mock_response.json.return_value = {"info": {"version": "2.0.0"}}

        mock_client = MagicMock(spec=httpx.Client)
        mock_client.get.return_value = mock_response
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)

        with patch("cli.update_checker.httpx.Client", return_value=mock_client):
            result = _fetch_latest_version_with_timeout()

        assert result == "2.0.0"

    def test_network_error_returns_none(self, monkeypatch):
        mock_client = MagicMock(spec=httpx.Client)
        mock_client.get.side_effect = httpx.ConnectError("Network error")
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)

        with patch("cli.update_checker.httpx.Client", return_value=mock_client):
            result = _fetch_latest_version_with_timeout()

        assert result is None

    def test_timeout_returns_none(self, monkeypatch):
        mock_client = MagicMock(spec=httpx.Client)
        mock_client.get.side_effect = httpx.TimeoutException("Timed out")
        mock_client.__enter__ = MagicMock(return_value=mock_client)
        mock_client.__exit__ = MagicMock(return_value=False)

        with patch("cli.update_checker.httpx.Client", return_value=mock_client):
            result = _fetch_latest_version_with_timeout()

        assert result is None


class TestCompareVersions:
    def test_update_available(self):
        assert _compare_versions("1.0.0", "2.0.0") == -1

    def test_up_to_date(self):
        assert _compare_versions("1.0.0", "1.0.0") == 0

    def test_ahead(self):
        assert _compare_versions("2.0.0", "1.0.0") == 1

    def test_invalid_version_returns_zero(self):
        assert _compare_versions("invalid", "1.0.0") == 0
        assert _compare_versions("1.0.0", "invalid") == 0


class TestRunStartupUpdateCheck:
    def test_skips_when_disabled(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.setenv(UPDATE_CHECK_ENV, "0")

        run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        assert captured.err == ""

    def test_skips_for_update_check_command(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        run_startup_update_check("1.0.0", invoked_command="update-check")

        captured = capsys.readouterr()
        assert captured.err == ""

    def test_skips_for_dev_version(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        run_startup_update_check("dev")

        captured = capsys.readouterr()
        assert captured.err == ""

    def test_skips_with_fresh_cache(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Create fresh cache
        recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
        cache_data = {
            "last_check": recent_time.isoformat(),
            "latest_version": "1.0.0",
            "installed_version": "1.0.0",
        }
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps(cache_data))

        run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        # No notification because up to date
        assert captured.err == ""

    def test_shows_notification_from_cache_when_update_available(
        self, tmp_path, monkeypatch, capsys
    ):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Create fresh cache with newer version
        recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
        cache_data = {
            "last_check": recent_time.isoformat(),
            "latest_version": "2.0.0",
            "installed_version": "1.0.0",
        }
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps(cache_data))

        run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        # Notification format per issue #184
        assert "ctxme v2.0.0 is available" in captured.err
        assert "you have v1.0.0" in captured.err

    def test_fetches_when_cache_stale(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Create stale cache
        old_time = datetime.now(timezone.utc) - timedelta(hours=25)
        cache_data = {
            "last_check": old_time.isoformat(),
            "latest_version": "1.0.0",
            "installed_version": "1.0.0",
        }
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps(cache_data))

        # Mock successful fetch returning update
        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value="2.0.0",
        ):
            run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        assert "ctxme v2.0.0 is available" in captured.err

        # Verify cache was updated
        updated_cache = json.loads(cache_file.read_text())
        assert updated_cache["latest_version"] == "2.0.0"

    def test_no_notification_when_up_to_date(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value="1.0.0",
        ):
            run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        assert captured.err == ""

    def test_no_notification_when_ahead(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value="1.0.0",
        ):
            run_startup_update_check("2.0.0")

        captured = capsys.readouterr()
        assert captured.err == ""

    def test_silent_on_network_failure(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value=None,
        ):
            run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        # No error output on network failure
        assert captured.err == ""

    def test_creates_cache_on_first_run_network_failure(self, tmp_path, monkeypatch, capsys):
        """Regression test: network failure on first run should still create cache.

        Without this, the CLI would retry on every invocation (retry storm).
        """
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Ensure no cache exists
        cache_file = tmp_path / CACHE_FILENAME
        assert not cache_file.exists()

        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value=None,
        ):
            run_startup_update_check("1.0.0")

        # Cache should be created even on network failure
        assert cache_file.exists()
        cache_data = json.loads(cache_file.read_text())
        assert "last_check" in cache_data
        # latest_version is set to installed_version as placeholder
        assert cache_data["latest_version"] == "1.0.0"
        assert cache_data["installed_version"] == "1.0.0"

    def test_updates_cache_timestamp_on_network_failure(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Create stale cache
        old_time = datetime.now(timezone.utc) - timedelta(hours=25)
        cache_data = {
            "last_check": old_time.isoformat(),
            "latest_version": "1.5.0",
            "installed_version": "1.0.0",
        }
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps(cache_data))

        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value=None,
        ):
            run_startup_update_check("1.0.0")

        # Cache should be updated with new timestamp
        updated_cache = json.loads(cache_file.read_text())
        new_check_time = datetime.fromisoformat(updated_cache["last_check"].replace("Z", "+00:00"))
        assert new_check_time > old_time

    def test_refetches_when_installed_version_changes(self, tmp_path, monkeypatch, capsys):
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Create fresh cache with OLD installed version
        recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
        cache_data = {
            "last_check": recent_time.isoformat(),
            "latest_version": "2.0.0",
            "installed_version": "0.9.0",  # Different from current
        }
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps(cache_data))

        # Mock fetch to return same version
        with patch(
            "cli.update_checker._fetch_latest_version_with_timeout",
            return_value="2.0.0",
        ):
            run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        # Should show notification because installed version changed
        assert "ctxme v2.0.0 is available" in captured.err


class TestGetCtxmeExecutablePath:
    """Tests for executable path detection with ctx alias support."""

    def test_finds_ctxme_first(self, monkeypatch):
        """ctxme is preferred over ctx to avoid collision with other ctx binaries."""
        from cli.update_checker import _get_ctxme_executable_path

        def mock_which(cmd):
            if cmd == "ctxme":
                return "/usr/local/bin/ctxme"
            if cmd == "ctx":
                return "/usr/local/bin/ctx"
            return None

        monkeypatch.setattr("cli.update_checker.shutil.which", mock_which)
        result = _get_ctxme_executable_path()
        assert result is not None
        assert "ctxme" in result

    def test_falls_back_to_ctx(self, monkeypatch):
        """Falls back to ctx when ctxme is not found."""
        from cli.update_checker import _get_ctxme_executable_path

        def mock_which(cmd):
            if cmd == "ctx":
                return "/usr/local/bin/ctx"
            return None

        monkeypatch.setattr("cli.update_checker.shutil.which", mock_which)
        result = _get_ctxme_executable_path()
        assert result is not None
        assert "ctx" in result

    def test_returns_none_when_neither_found(self, monkeypatch):
        """Returns None when neither ctxme nor ctx is found."""
        from cli.update_checker import _get_ctxme_executable_path

        monkeypatch.setattr("cli.update_checker.shutil.which", lambda cmd: None)
        assert _get_ctxme_executable_path() is None


class TestDetectInstallMethod:
    """Tests for install method detection.

    Detection is based on the ctxme executable path (via shutil.which),
    NOT sys.executable, to avoid misclassifying pip installs when using
    Homebrew Python.
    """

    def test_detects_homebrew_opt_prefix(self, monkeypatch):
        # ctxme installed via Homebrew is in /opt/homebrew/bin/ctxme
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: "/opt/homebrew/bin/ctxme",
        )
        assert detect_install_method() == "brew"

    def test_detects_homebrew_usr_local_prefix(self, monkeypatch):
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: "/usr/local/Cellar/ctxme/1.0.0/bin/ctxme",
        )
        assert detect_install_method() == "brew"

    def test_detects_homebrew_linuxbrew_prefix(self, monkeypatch):
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: "/home/linuxbrew/.linuxbrew/bin/ctxme",
        )
        assert detect_install_method() == "brew"

    def test_detects_pipx_with_venv(self, tmp_path, monkeypatch):
        # Create pipx venv directory
        pipx_venv = tmp_path / ".local" / "pipx" / "venvs" / "ctxme"
        pipx_venv.mkdir(parents=True)

        # Clear env vars that might be set on CI to use defaults
        monkeypatch.delenv("PIPX_BIN_DIR", raising=False)
        monkeypatch.delenv("PIPX_HOME", raising=False)
        # Mock home to use tmp_path
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        # Mock ctxme executable to be in pipx bin
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(tmp_path / ".local/bin/ctxme"),
        )

        assert detect_install_method() == "pipx"

    def test_pipx_requires_venv_exists(self, tmp_path, monkeypatch):
        # Clear env vars that might be set on CI to use defaults
        monkeypatch.delenv("PIPX_BIN_DIR", raising=False)
        monkeypatch.delenv("PIPX_HOME", raising=False)
        # No pipx venv directory created
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(tmp_path / ".local/bin/ctxme"),
        )

        # Should fall back to pip because venv doesn't exist
        assert detect_install_method() == "pip"

    def test_pipx_respects_pipx_home_env(self, tmp_path, monkeypatch):
        # Create pipx venv in custom PIPX_HOME location
        custom_pipx_home = tmp_path / "custom" / "pipx"
        pipx_venv = custom_pipx_home / "venvs" / "ctxme"
        pipx_venv.mkdir(parents=True)

        # Clear PIPX_BIN_DIR to use default
        monkeypatch.delenv("PIPX_BIN_DIR", raising=False)
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        monkeypatch.setenv("PIPX_HOME", str(custom_pipx_home))
        # ctxme in default bin dir
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(tmp_path / ".local/bin/ctxme"),
        )

        assert detect_install_method() == "pipx"

    def test_pipx_respects_pipx_bin_dir_env(self, tmp_path, monkeypatch):
        # Create pipx venv in default location
        pipx_venv = tmp_path / ".local" / "pipx" / "venvs" / "ctxme"
        pipx_venv.mkdir(parents=True)

        custom_bin_dir = tmp_path / "custom" / "bin"
        custom_bin_dir.mkdir(parents=True)

        # Clear PIPX_HOME to use default
        monkeypatch.delenv("PIPX_HOME", raising=False)
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        monkeypatch.setenv("PIPX_BIN_DIR", str(custom_bin_dir))
        # ctxme in custom bin dir
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(custom_bin_dir / "ctxme"),
        )

        assert detect_install_method() == "pipx"

    def test_pipx_respects_both_env_vars(self, tmp_path, monkeypatch):
        # Create pipx venv in fully custom location
        custom_pipx_home = tmp_path / "opt" / "pipx"
        custom_bin_dir = tmp_path / "opt" / "bin"
        pipx_venv = custom_pipx_home / "venvs" / "ctxme"
        pipx_venv.mkdir(parents=True)
        custom_bin_dir.mkdir(parents=True)

        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        monkeypatch.setenv("PIPX_HOME", str(custom_pipx_home))
        monkeypatch.setenv("PIPX_BIN_DIR", str(custom_bin_dir))
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(custom_bin_dir / "ctxme"),
        )

        assert detect_install_method() == "pipx"

    def test_defaults_to_pip(self, tmp_path, monkeypatch):
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        # ctxme in standard system location (not brew or pipx)
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: "/usr/local/bin/ctxme",
        )
        assert detect_install_method() == "pip"

    def test_defaults_to_pip_on_venv(self, tmp_path, monkeypatch):
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        # ctxme in a project venv
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(tmp_path / "myproject" / ".venv" / "bin" / "ctxme"),
        )
        assert detect_install_method() == "pip"

    def test_defaults_to_pip_when_ctxme_not_found(self, monkeypatch):
        # shutil.which returns None when ctxme is not found
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: None,
        )
        assert detect_install_method() == "pip"

    def test_pip_install_with_homebrew_python(self, tmp_path, monkeypatch):
        """Verify pip install isn't misclassified when using Homebrew Python.

        This is a regression test for the case where ctxme is pip-installed
        but the Python interpreter is from Homebrew. The detection should
        check the ctxme executable path, not sys.executable.
        """
        # ctxme is pip-installed to user site-packages
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: str(tmp_path / ".local" / "bin" / "ctxme"),
        )
        # Clear pipx env vars
        monkeypatch.delenv("PIPX_BIN_DIR", raising=False)
        monkeypatch.delenv("PIPX_HOME", raising=False)
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)
        # No pipx venv exists, so should be "pip"
        assert detect_install_method() == "pip"


class TestGetUpgradeCommand:
    def test_brew_command(self):
        assert get_upgrade_command("brew") == "brew upgrade ctxme"

    def test_pipx_command(self):
        assert get_upgrade_command("pipx") == "pipx upgrade ctxme"

    def test_pip_command(self):
        assert get_upgrade_command("pip") == "pip install --upgrade ctxme"

    def test_unknown_defaults_to_pip(self):
        assert get_upgrade_command("unknown") == "pip install --upgrade ctxme"


class TestFormatUpdateNotification:
    def test_format_with_pip(self):
        result = format_update_notification("1.0.0", "2.0.0", install_method="pip")
        assert "ctxme v2.0.0 is available (you have v1.0.0)" in result
        assert "pip install --upgrade ctxme" in result

    def test_format_with_brew(self):
        result = format_update_notification("1.0.0", "2.0.0", install_method="brew")
        assert "ctxme v2.0.0 is available (you have v1.0.0)" in result
        assert "brew upgrade ctxme" in result

    def test_format_with_pipx(self):
        result = format_update_notification("1.0.0", "2.0.0", install_method="pipx")
        assert "ctxme v2.0.0 is available (you have v1.0.0)" in result
        assert "pipx upgrade ctxme" in result

    def test_auto_detects_install_method(self, tmp_path, monkeypatch):
        # Default to pip for this test (ctxme in standard location)
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: "/usr/local/bin/ctxme",
        )
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)

        result = format_update_notification("1.0.0", "2.0.0")
        assert "pip install --upgrade ctxme" in result


class TestUpdateNotificationIntegration:
    def test_notification_uses_correct_format(self, tmp_path, monkeypatch, capsys):
        """Verify the notification message matches the spec from issue #184."""
        monkeypatch.setenv(CACHE_DIR_ENV, str(tmp_path))
        monkeypatch.delenv(UPDATE_CHECK_ENV, raising=False)

        # Create fresh cache with newer version
        recent_time = datetime.now(timezone.utc) - timedelta(hours=1)
        cache_data = {
            "last_check": recent_time.isoformat(),
            "latest_version": "2.0.0",
            "installed_version": "1.0.0",
        }
        cache_file = tmp_path / CACHE_FILENAME
        cache_file.write_text(json.dumps(cache_data))

        # Mock pip install method (ctxme in standard location)
        monkeypatch.setattr(
            "cli.update_checker._get_ctxme_executable_path",
            lambda: "/usr/local/bin/ctxme",
        )
        monkeypatch.setattr("cli.update_checker.Path.home", lambda: tmp_path)

        run_startup_update_check("1.0.0")

        captured = capsys.readouterr()
        # Verify the exact format from issue #184
        assert "Note: ctxme v2.0.0 is available (you have v1.0.0)" in captured.err
        assert "Run 'pip install --upgrade ctxme' to update" in captured.err
