"""
Module: test_update_checker
Description: Tests for dependency update checking

Implements:
    - docs/cli/reference.md#outdated-dependencies
"""

import json
import time
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock, patch

import httpx
from rich.console import Console

from huitzo_cli.update_checker import (
    CACHE_MAX_AGE_SECONDS,
    UpdateInfo,
    _fetch_latest_version,
    _get_installed_version,
    _is_cache_fresh,
    _is_outdated,
    _load_cache,
    _save_cache,
    check_for_updates,
    collect_and_warn,
    should_skip,
    start_update_check,
)


class TestGetInstalledVersion:
    def test_returns_version_for_installed_package(self) -> None:
        with patch("huitzo_cli.update_checker.version", return_value="1.2.3"):
            assert _get_installed_version("some-package") == "1.2.3"

    def test_returns_none_for_missing_package(self) -> None:
        from importlib.metadata import PackageNotFoundError

        with patch("huitzo_cli.update_checker.version", side_effect=PackageNotFoundError):
            assert _get_installed_version("nonexistent") is None


class TestFetchLatestVersion:
    def test_success(self) -> None:
        mock_resp = MagicMock()
        mock_resp.json.return_value = {"info": {"version": "2.0.0"}}
        mock_resp.raise_for_status = MagicMock()
        with patch("huitzo_cli.update_checker.httpx.get", return_value=mock_resp):
            assert _fetch_latest_version("some-package") == "2.0.0"

    def test_network_error_returns_none(self) -> None:
        with patch("huitzo_cli.update_checker.httpx.get", side_effect=httpx.ConnectError("fail")):
            assert _fetch_latest_version("some-package") is None

    def test_timeout_returns_none(self) -> None:
        with patch(
            "huitzo_cli.update_checker.httpx.get", side_effect=httpx.TimeoutException("timeout")
        ):
            assert _fetch_latest_version("some-package") is None


class TestIsOutdated:
    def test_older_version(self) -> None:
        assert _is_outdated("0.0.1", "0.0.2") is True

    def test_same_version(self) -> None:
        assert _is_outdated("1.0.0", "1.0.0") is False

    def test_newer_version(self) -> None:
        assert _is_outdated("2.0.0", "1.0.0") is False

    def test_invalid_version_returns_false(self) -> None:
        assert _is_outdated("not-a-version", "1.0.0") is False


class TestCache:
    def test_load_cache_missing_file(self, temp_dir: Path) -> None:
        assert _load_cache(temp_dir / "nonexistent.json") == {}

    def test_load_cache_corrupted(self, temp_dir: Path) -> None:
        cache_file = temp_dir / "cache.json"
        cache_file.write_text("not valid json{{{")
        assert _load_cache(cache_file) == {}

    def test_load_and_save_roundtrip(self, temp_dir: Path) -> None:
        cache_file = temp_dir / "cache.json"
        versions = {"huitzo": "1.0.0", "huitzo-sdk": "0.0.2"}
        _save_cache(cache_file, versions)
        data = _load_cache(cache_file)
        assert data["versions"] == versions
        assert isinstance(data["timestamp"], float)

    def test_cache_fresh_within_24h(self) -> None:
        data: dict[str, Any] = {"timestamp": time.time() - 100}
        assert _is_cache_fresh(data) is True

    def test_cache_stale_after_24h(self) -> None:
        data: dict[str, Any] = {"timestamp": time.time() - CACHE_MAX_AGE_SECONDS - 1}
        assert _is_cache_fresh(data) is False

    def test_cache_missing_timestamp(self) -> None:
        assert _is_cache_fresh({}) is False

    def test_cache_invalid_timestamp(self) -> None:
        assert _is_cache_fresh({"timestamp": "not-a-number"}) is False


class TestCheckForUpdates:
    @patch("huitzo_cli.update_checker._get_cache_path")
    @patch("huitzo_cli.update_checker._fetch_latest_version")
    @patch("huitzo_cli.update_checker._get_installed_version")
    def test_outdated_package(
        self,
        mock_installed: MagicMock,
        mock_fetch: MagicMock,
        mock_cache_path: MagicMock,
        temp_dir: Path,
    ) -> None:
        mock_cache_path.return_value = temp_dir / "cache.json"
        mock_installed.return_value = "0.0.1"
        mock_fetch.return_value = "0.0.2"

        results = check_for_updates(["huitzo-sdk"])
        assert len(results) == 1
        assert results[0] == UpdateInfo("huitzo-sdk", "0.0.1", "0.0.2")

    @patch("huitzo_cli.update_checker._get_cache_path")
    @patch("huitzo_cli.update_checker._fetch_latest_version")
    @patch("huitzo_cli.update_checker._get_installed_version")
    def test_up_to_date(
        self,
        mock_installed: MagicMock,
        mock_fetch: MagicMock,
        mock_cache_path: MagicMock,
        temp_dir: Path,
    ) -> None:
        mock_cache_path.return_value = temp_dir / "cache.json"
        mock_installed.return_value = "1.0.0"
        mock_fetch.return_value = "1.0.0"

        results = check_for_updates(["huitzo"])
        assert len(results) == 0

    @patch("huitzo_cli.update_checker._get_cache_path")
    @patch("huitzo_cli.update_checker._fetch_latest_version")
    @patch("huitzo_cli.update_checker._get_installed_version")
    def test_uses_cache_when_fresh(
        self,
        mock_installed: MagicMock,
        mock_fetch: MagicMock,
        mock_cache_path: MagicMock,
        temp_dir: Path,
    ) -> None:
        cache_file = temp_dir / "cache.json"
        cache_file.write_text(
            json.dumps({"timestamp": time.time(), "versions": {"huitzo-sdk": "0.0.2"}})
        )
        mock_cache_path.return_value = cache_file
        mock_installed.return_value = "0.0.1"

        results = check_for_updates(["huitzo-sdk"])
        assert len(results) == 1
        # Should NOT have called PyPI
        mock_fetch.assert_not_called()


class TestShouldSkip:
    def test_cli_flag(self) -> None:
        assert should_skip(True) is True

    def test_no_flag(self) -> None:
        with patch.dict("os.environ", {}, clear=False):
            # Remove env var if set
            import os

            os.environ.pop("HUITZO_SKIP_UPDATE_CHECK", None)
            assert should_skip(False) is False

    def test_env_var_1(self) -> None:
        with patch.dict("os.environ", {"HUITZO_SKIP_UPDATE_CHECK": "1"}):
            assert should_skip(False) is True

    def test_env_var_true(self) -> None:
        with patch.dict("os.environ", {"HUITZO_SKIP_UPDATE_CHECK": "true"}):
            assert should_skip(False) is True

    def test_env_var_yes(self) -> None:
        with patch.dict("os.environ", {"HUITZO_SKIP_UPDATE_CHECK": "yes"}):
            assert should_skip(False) is True


class TestCollectAndWarn:
    @patch("huitzo_cli.update_checker._get_cache_path")
    @patch("huitzo_cli.update_checker._fetch_latest_version")
    @patch("huitzo_cli.update_checker._get_installed_version")
    def test_prints_warning(
        self,
        mock_installed: MagicMock,
        mock_fetch: MagicMock,
        mock_cache_path: MagicMock,
        temp_dir: Path,
    ) -> None:
        mock_cache_path.return_value = temp_dir / "cache.json"
        mock_installed.return_value = "0.0.0"
        mock_fetch.return_value = "0.0.2"

        from io import StringIO

        buf = StringIO()
        thread = start_update_check(["huitzo-sdk"])
        console = Console(file=buf, no_color=True, force_terminal=False, width=200)
        collect_and_warn(thread, console)

        full_output = buf.getvalue()
        assert "huitzo-sdk" in full_output
        assert "0.0.2" in full_output
        assert "pip install --upgrade" in full_output

    @patch("huitzo_cli.update_checker._get_cache_path")
    @patch("huitzo_cli.update_checker._fetch_latest_version")
    @patch("huitzo_cli.update_checker._get_installed_version")
    def test_no_output_when_up_to_date(
        self,
        mock_installed: MagicMock,
        mock_fetch: MagicMock,
        mock_cache_path: MagicMock,
        temp_dir: Path,
    ) -> None:
        mock_cache_path.return_value = temp_dir / "cache.json"
        mock_installed.return_value = "1.0.0"
        mock_fetch.return_value = "1.0.0"

        from io import StringIO

        buf = StringIO()
        thread = start_update_check(["huitzo"])
        console = Console(file=buf, no_color=True, force_terminal=False)
        collect_and_warn(thread, console)

        full_output = buf.getvalue()
        assert "Warning" not in full_output
