"""Tests for PluginService — list, install, uninstall, search."""

from __future__ import annotations

import importlib.metadata
from unittest.mock import MagicMock, patch

import pytest

from kuberoku.factory import KuberokuFactory
from kuberoku.sdk.plugins import PluginService
from tests.backends.fake import FakeK8sClient

_PYPI_MONGODB = (
    b'{"info": {"name": "kuberoku-mongodb", "version": "1.0.0", "summary": "MongoDB addon"}}'
)

_PYPI_TEST = b'{"info": {"name": "kuberoku-test", "version": "1.0.0", "summary": "Test"}}'


@pytest.fixture()
def plugin_service(tmp_namespace: str) -> PluginService:
    factory = KuberokuFactory(k8s_client=FakeK8sClient(), namespace=tmp_namespace)
    return factory.plugins


class TestListInstalled:
    def test_no_plugins(self, plugin_service: PluginService) -> None:
        with patch(
            "kuberoku.sdk.plugins.importlib.metadata.entry_points",
            return_value=[],
        ):
            result = plugin_service.list_installed()
        assert result == ()

    def test_single_plugin(self, plugin_service: PluginService) -> None:
        ep = MagicMock()
        ep.dist = MagicMock()
        ep.dist.name = "kuberoku-mongodb"
        ep.dist.version = "1.0.0"
        ep.dist.metadata = {"Summary": "MongoDB addon"}

        with patch(
            "kuberoku.sdk.plugins.importlib.metadata.entry_points",
            return_value=[ep],
        ):
            result = plugin_service.list_installed()
        assert len(result) == 1
        assert result[0].name == "mongodb"
        assert result[0].package == "kuberoku-mongodb"
        assert result[0].version == "1.0.0"
        assert result[0].description == "MongoDB addon"
        assert result[0].installed is True

    def test_multiple_plugins_sorted(self, plugin_service: PluginService) -> None:
        ep1 = MagicMock()
        ep1.dist = MagicMock()
        ep1.dist.name = "kuberoku-zookeeper"
        ep1.dist.version = "2.0.0"
        ep1.dist.metadata = {"Summary": "Zookeeper"}

        ep2 = MagicMock()
        ep2.dist = MagicMock()
        ep2.dist.name = "kuberoku-alpha"
        ep2.dist.version = "0.1.0"
        ep2.dist.metadata = {"Summary": "Alpha plugin"}

        with patch(
            "kuberoku.sdk.plugins.importlib.metadata.entry_points",
            return_value=[ep1, ep2],
        ):
            result = plugin_service.list_installed()
        assert len(result) == 2
        assert result[0].name == "alpha"
        assert result[1].name == "zookeeper"

    def test_plugin_with_none_dist(self, plugin_service: PluginService) -> None:
        ep = MagicMock()
        ep.dist = None
        with patch(
            "kuberoku.sdk.plugins.importlib.metadata.entry_points",
            return_value=[ep],
        ):
            result = plugin_service.list_installed()
        assert result == ()


class TestInstall:
    def test_install_runs_pip(self, plugin_service: PluginService) -> None:
        mock_dist = MagicMock()
        mock_dist.version = "1.0.0"
        mock_dist.metadata = {"Summary": "Test"}

        with (
            patch("kuberoku.sdk.plugins.subprocess.run") as mock_run,
            patch(
                "kuberoku.sdk.plugins.importlib.metadata.distribution",
                return_value=mock_dist,
            ),
            patch(
                "kuberoku.sdk.plugins.importlib.metadata.entry_points",
                MagicMock(cache_clear=MagicMock()),
            ),
        ):
            result = plugin_service.install("kuberoku-mongodb")

        mock_run.assert_called_once()
        assert result.name == "mongodb"
        assert result.installed is True

    def test_install_calls_on_step(self, plugin_service: PluginService) -> None:
        steps: list[str] = []
        mock_dist = MagicMock()
        mock_dist.version = "1.0.0"
        mock_dist.metadata = {"Summary": ""}

        with (
            patch("kuberoku.sdk.plugins.subprocess.run"),
            patch(
                "kuberoku.sdk.plugins.importlib.metadata.distribution",
                return_value=mock_dist,
            ),
            patch(
                "kuberoku.sdk.plugins.importlib.metadata.entry_points",
                MagicMock(cache_clear=MagicMock()),
            ),
        ):
            plugin_service.install("kuberoku-test", on_step=steps.append)

        assert any("Installing" in s for s in steps)

    def test_install_frozen_raises(self, plugin_service: PluginService) -> None:
        with (
            patch("kuberoku.sdk.plugins.sys") as mock_sys,
            pytest.raises(Exception, match="not supported in frozen"),
        ):
            mock_sys.frozen = True
            plugin_service.install("kuberoku-test")


class TestUninstall:
    def test_uninstall_runs_pip(self, plugin_service: PluginService) -> None:
        with patch("kuberoku.sdk.plugins.subprocess.run") as mock_run:
            plugin_service.uninstall("kuberoku-mongodb")

        mock_run.assert_called_once()

    def test_uninstall_calls_on_step(self, plugin_service: PluginService) -> None:
        steps: list[str] = []
        with patch("kuberoku.sdk.plugins.subprocess.run"):
            plugin_service.uninstall("kuberoku-test", on_step=steps.append)
        assert any("Uninstalling" in s for s in steps)


class TestSearch:
    def test_search_returns_results(self, plugin_service: PluginService) -> None:
        mock_response = MagicMock()
        mock_response.read.return_value = _PYPI_MONGODB
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)

        with (
            patch("urllib.request.urlopen", return_value=mock_response),
            patch(
                "kuberoku.sdk.plugins.importlib.metadata.distribution",
                side_effect=importlib.metadata.PackageNotFoundError("not installed"),
            ),
        ):
            result = plugin_service.search("mongodb")
        assert len(result) == 1
        assert result[0].name == "mongodb"
        assert result[0].installed is False

    def test_search_network_error(self, plugin_service: PluginService) -> None:
        with patch("urllib.request.urlopen", side_effect=Exception("timeout")):
            result = plugin_service.search("nonexistent")
        assert result == ()

    def test_search_installed_package(self, plugin_service: PluginService) -> None:
        mock_response = MagicMock()
        mock_response.read.return_value = _PYPI_TEST
        mock_response.__enter__ = MagicMock(return_value=mock_response)
        mock_response.__exit__ = MagicMock(return_value=False)

        mock_dist = MagicMock()
        with (
            patch("urllib.request.urlopen", return_value=mock_response),
            patch(
                "kuberoku.sdk.plugins.importlib.metadata.distribution",
                return_value=mock_dist,
            ),
        ):
            result = plugin_service.search("test")
        assert len(result) == 1
        assert result[0].installed is True
