"""Tests for the PyPI upgrade nudge in cli._check_for_update()."""

import importlib
import importlib.util
from datetime import datetime, timezone, timedelta
from unittest.mock import patch, MagicMock

import pytest
import yaml

if importlib.util.find_spec("gjalla_precommit") is None:  # pragma: no cover
    pytest.skip("gjalla_precommit package not available", allow_module_level=True)

cli_module = importlib.import_module("gjalla_precommit.cli")


class TestCheckForUpdate:
    """Tests for _check_for_update()."""

    def _write_config(self, home_dir, data):
        config_dir = home_dir / ".gjalla"
        config_dir.mkdir(parents=True, exist_ok=True)
        (config_dir / "config.yaml").write_text(yaml.dump(data))

    def _read_config(self, home_dir):
        return yaml.safe_load((home_dir / ".gjalla" / "config.yaml").read_text())

    def _mock_pypi_response(self, version="99.0.0"):
        resp = MagicMock()
        resp.json.return_value = {"info": {"version": version}}
        resp.raise_for_status = MagicMock()
        return resp

    def test_nudge_shown_when_newer_version(self, home_dir, capsys):
        """Shows nudge when PyPI has a newer version."""
        self._write_config(home_dir, {})
        resp = self._mock_pypi_response("99.0.0")

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get", return_value=resp) as mock_get:
            mock_stderr.isatty.return_value = True
            cli_module._check_for_update()

        mock_get.assert_called_once_with(
            "https://pypi.org/pypi/gjalla/json", timeout=2
        )
        config = self._read_config(home_dir)
        assert config["latest_pypi_version"] == "99.0.0"
        assert "last_version_check" in config

    def test_no_nudge_when_current(self, home_dir, capsys):
        """No nudge when already on latest version."""
        self._write_config(home_dir, {})
        resp = self._mock_pypi_response(cli_module.__version__)

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get", return_value=resp):
            mock_stderr.isatty.return_value = True
            cli_module._check_for_update()

        # Config still updated with timestamp
        config = self._read_config(home_dir)
        assert config["latest_pypi_version"] == cli_module.__version__

    def test_skips_when_checked_recently(self, home_dir):
        """Skips PyPI check if last check was within 24 hours."""
        recent = datetime.now(timezone.utc) - timedelta(hours=1)
        self._write_config(home_dir, {
            "last_version_check": recent.isoformat(),
        })

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get") as mock_get:
            mock_stderr.isatty.return_value = True
            cli_module._check_for_update()

        mock_get.assert_not_called()

    def test_checks_when_cache_expired(self, home_dir):
        """Hits PyPI when last check was >24 hours ago."""
        old = datetime.now(timezone.utc) - timedelta(hours=25)
        self._write_config(home_dir, {
            "last_version_check": old.isoformat(),
        })
        resp = self._mock_pypi_response("99.0.0")

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get", return_value=resp) as mock_get:
            mock_stderr.isatty.return_value = True
            cli_module._check_for_update()

        mock_get.assert_called_once()

    def test_skips_when_not_tty(self, home_dir):
        """Does nothing when stderr is not a TTY."""
        self._write_config(home_dir, {})

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get") as mock_get:
            mock_stderr.isatty.return_value = False
            cli_module._check_for_update()

        mock_get.assert_not_called()

    def test_swallows_network_errors(self, home_dir):
        """Never raises — network errors are silently caught."""
        self._write_config(home_dir, {})

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get", side_effect=Exception("network down")):
            mock_stderr.isatty.return_value = True
            cli_module._check_for_update()  # should not raise

    def test_swallows_bad_json(self, home_dir):
        """Handles malformed PyPI JSON gracefully."""
        self._write_config(home_dir, {})
        resp = MagicMock()
        resp.json.return_value = {}  # missing "info" key
        resp.raise_for_status = MagicMock()

        with patch("sys.stderr") as mock_stderr, \
             patch("httpx.get", return_value=resp):
            mock_stderr.isatty.return_value = True
            cli_module._check_for_update()  # should not raise
