"""Tests for plugin discovery and mounting via entry_points."""

from __future__ import annotations

import warnings
from unittest.mock import MagicMock, patch

import click

from kuberoku.addons import ADDON_REGISTRY, _discover_addon_plugins, register
from kuberoku.addons._types import AddonDef
from kuberoku.plugins.loader import CORE_COMMANDS, load_plugins


def _make_entry_point(
    ep_name: str,
    package_name: str,
    plugin_group: click.Group | None = None,
) -> MagicMock:
    """Create a mock entry_point with dist metadata."""
    ep = MagicMock()
    ep.name = ep_name
    ep.dist = MagicMock()
    ep.dist.name = package_name

    if plugin_group is None:
        plugin_group = click.Group(name=ep_name)
    ep.load.return_value = plugin_group
    return ep


class TestLoadPlugins:
    def test_no_plugins_installed(self) -> None:
        root = click.Group(name="cli")
        with patch("kuberoku.plugins.loader.importlib.metadata.entry_points", return_value=[]):
            loaded = load_plugins(root)
        assert loaded == []

    def test_single_plugin_loaded(self) -> None:
        root = click.Group(name="cli")
        ep = _make_entry_point("mongodb", "kuberoku-mongodb")
        with patch("kuberoku.plugins.loader.importlib.metadata.entry_points", return_value=[ep]):
            loaded = load_plugins(root)
        assert loaded == ["mongodb"]
        assert "mongodb" in root.commands

    def test_multiple_plugins_loaded(self) -> None:
        root = click.Group(name="cli")
        ep1 = _make_entry_point("mongodb", "kuberoku-mongodb")
        ep2 = _make_entry_point("mysql", "kuberoku-mysql")
        with patch(
            "kuberoku.plugins.loader.importlib.metadata.entry_points",
            return_value=[ep1, ep2],
        ):
            loaded = load_plugins(root)
        assert "mongodb" in loaded
        assert "mysql" in loaded
        assert "mongodb" in root.commands
        assert "mysql" in root.commands

    def test_plugin_cannot_override_core_command(self) -> None:
        root = click.Group(name="cli")
        ep = _make_entry_point("apps", "kuberoku-apps")
        with (
            patch(
                "kuberoku.plugins.loader.importlib.metadata.entry_points",
                return_value=[ep],
            ),
            warnings.catch_warnings(record=True) as w,
        ):
            warnings.simplefilter("always")
            loaded = load_plugins(root)
        assert loaded == []
        assert len(w) == 1
        assert "conflicts with core command" in str(w[0].message)

    def test_all_core_commands_protected(self) -> None:
        expected = {
            "apps",
            "config",
            "deploy",
            "ps",
            "releases",
            "services",
            "addons",
            "domains",
            "clusters",
            "plugins",
        }
        assert expected == CORE_COMMANDS

    def test_plugin_failure_warns_but_continues(self) -> None:
        root = click.Group(name="cli")
        ep_bad = _make_entry_point("broken", "kuberoku-broken")
        ep_bad.load.side_effect = ImportError("boom")
        ep_good = _make_entry_point("mongodb", "kuberoku-mongodb")

        with (
            patch(
                "kuberoku.plugins.loader.importlib.metadata.entry_points",
                return_value=[ep_bad, ep_good],
            ),
            warnings.catch_warnings(record=True) as w,
        ):
            warnings.simplefilter("always")
            loaded = load_plugins(root)
        assert loaded == ["mongodb"]
        assert any("Failed to load" in str(warning.message) for warning in w)

    def test_plugin_with_none_dist_skipped(self) -> None:
        root = click.Group(name="cli")
        ep = MagicMock()
        ep.name = "test"
        ep.dist = None
        with patch(
            "kuberoku.plugins.loader.importlib.metadata.entry_points",
            return_value=[ep],
        ):
            loaded = load_plugins(root)
        assert loaded == []

    def test_frozen_binary_returns_empty(self) -> None:
        root = click.Group(name="cli")
        with patch("kuberoku.plugins.loader._is_frozen", return_value=True):
            loaded = load_plugins(root)
        assert loaded == []

    def test_command_name_strips_prefix(self) -> None:
        root = click.Group(name="cli")
        ep = _make_entry_point("mongo", "kuberoku-mongo")
        with patch(
            "kuberoku.plugins.loader.importlib.metadata.entry_points",
            return_value=[ep],
        ):
            loaded = load_plugins(root)
        assert loaded == ["mongo"]
        assert "mongo" in root.commands


class TestAddonPluginDiscovery:
    def test_builtin_types_protected(self) -> None:
        assert "postgres" in ADDON_REGISTRY
        assert "redis" in ADDON_REGISTRY

    def test_addon_plugin_discovered_and_registered(self) -> None:
        mock_def = AddonDef(
            type_name="mongodb",
            image="mongo:7",
            port=27017,
            protocol="TCP",
            default_cpu="250m",
            default_memory="256Mi",
            default_storage="1Gi",
            needs_storage=True,
            storage_optional=False,
            needs_auth=True,
            env_key="MONGODB_URL",
            url_template="mongodb://{user}:{password}@{host}:{port}/{db}",
            data_dir="/data/db",
            volume_name="mongo-data",
            readiness_probe={
                "exec": {"command": ["mongosh", "--eval", "db.runCommand('ping')"]},
                "initialDelaySeconds": 5,
                "periodSeconds": 5,
            },
            exec_command=["mongosh"],
            backup_command=None,
            restore_command=None,
            container_env_builder=None,
        )

        register(mock_def)

        assert "mongodb" in ADDON_REGISTRY
        assert ADDON_REGISTRY["mongodb"].port == 27017

        # Cleanup
        del ADDON_REGISTRY["mongodb"]

    def test_builtin_addon_plugin_conflict_skipped(self) -> None:
        """Addon plugin that conflicts with builtin type should be skipped."""
        ep = MagicMock()
        ep.name = "postgres"
        ep.load.return_value = MagicMock()

        with (
            patch(
                "kuberoku.addons.importlib.metadata.entry_points",
                return_value=[ep],
            ),
            warnings.catch_warnings(record=True) as w,
        ):
            warnings.simplefilter("always")
            _discover_addon_plugins()
        assert any("conflicts with built-in" in str(warning.message) for warning in w)
