# #region Test.Core.ClientRegistry [C:3] [TYPE Module] [SEMANTICS test, client, registry, async, superset]
# @BRIEF Tests for SupersetClientRegistry — singleton async client management.
# @RELATION BINDS_TO -> [SupersetClientRegistryModule]

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))

import asyncio
from unittest.mock import AsyncMock, MagicMock, patch

import pytest


@pytest.fixture(autouse=True)
def clear_registry():
    """Clear the module-level registry between tests."""
    import src.core.utils.client_registry as cr
    cr._registry.clear()


class TestBuildEnvId:
    def test_with_env_object(self):
        from src.core.utils.client_registry import _build_env_id
        env = MagicMock()
        env.id = "env-1"
        env.base_url = "https://superset.example.com"
        result = _build_env_id(env)
        assert "env-1" in result
        assert "superset.example.com" in result

    def test_with_env_no_id(self):
        from src.core.utils.client_registry import _build_env_id
        env = MagicMock()
        env.id = None
        env.base_url = "https://test.com"
        result = _build_env_id(env)
        assert result is not None

    def test_with_dict(self):
        from src.core.utils.client_registry import _build_env_id
        env = {"base_url": "https://test.com", "username": "admin"}
        result = _build_env_id(env)
        assert "test.com" in result

    def test_with_unknown(self):
        from src.core.utils.client_registry import _build_env_id
        result = _build_env_id(object())
        assert result is not None


class TestGetClient:
    @pytest.mark.asyncio
    async def test_fast_path_returns_existing(self):
        from src.core.utils.client_registry import get_client, _registry
        from src.core.utils.client_registry import _ClientSlot
        from src.core.utils.async_network import AsyncAPIClient

        # Pre-populate registry
        mock_client = AsyncMock(spec=AsyncAPIClient)
        _registry["test-id"] = _ClientSlot(
            async_client=mock_client,
            semaphore=asyncio.Semaphore(1),
            lock=asyncio.Lock(),
        )
        env = MagicMock()
        env.id = "test"
        env.base_url = "https://test.com"
        result = await get_client(env)
        assert result is mock_client

    @pytest.mark.asyncio
    async def test_creates_new_for_env_object(self):
        """Environment model → creates client with standard config."""
        from src.core.utils.client_registry import get_client

        env = MagicMock()
        env.id = "env1"
        env.url = "https://superset.example.com"
        env.username = "admin"
        env.password = "pass"
        env.verify_ssl = True

        with patch("src.core.utils.client_registry.AsyncAPIClient") as mock_client_cls:
            mock_instance = AsyncMock()
            mock_client_cls.return_value = mock_instance
            result = await get_client(env)
            assert result is mock_instance
            mock_client_cls.assert_called_once()
            config = mock_client_cls.call_args[1]["config"]
            assert config["base_url"] == "https://superset.example.com"
            assert config["auth"]["username"] == "admin"

    @pytest.mark.asyncio
    async def test_creates_new_for_dict(self):
        """Dict config → creates client."""
        from src.core.utils.client_registry import get_client

        env = {"base_url": "https://test.com", "auth": {"username": "admin", "password": "pass"}}

        with patch("src.core.utils.client_registry.AsyncAPIClient") as mock_client_cls:
            mock_instance = AsyncMock()
            mock_client_cls.return_value = mock_instance
            result = await get_client(env)
            assert result is mock_instance

    @pytest.mark.asyncio
    async def test_creates_new_for_pydantic_model(self):
        """Pydantic model with model_dump → creates client."""
        from src.core.utils.client_registry import get_client

        env = MagicMock()
        # Has model_dump but not hasattr url/username
        env.model_dump.return_value = {"base_url": "https://test.com", "verify_ssl": True}

        with patch("src.core.utils.client_registry.AsyncAPIClient") as mock_client_cls:
            mock_instance = AsyncMock()
            mock_client_cls.return_value = mock_instance
            result = await get_client(env)
            assert result is mock_instance

    @pytest.mark.asyncio
    async def test_fallback_empty_config(self):
        """Unknown env type → empty config."""
        from src.core.utils.client_registry import get_client

        env = 42  # int, no attributes

        with patch("src.core.utils.client_registry.AsyncAPIClient") as mock_client_cls:
            mock_instance = AsyncMock()
            mock_client_cls.return_value = mock_instance
            result = await get_client(env)
            assert result is mock_instance
            # Should pass empty config
            config = mock_client_cls.call_args[1].get("config", {})
            assert config == {}

    @pytest.mark.asyncio
    async def test_verify_ssl_false(self):
        """verify_ssl=False → no SSL context."""
        from src.core.utils.client_registry import get_client

        env = {"base_url": "https://test.com", "auth": {}, "verify_ssl": False}

        with patch("src.core.utils.client_registry.AsyncAPIClient") as mock_client_cls:
            mock_instance = AsyncMock()
            mock_client_cls.return_value = mock_instance
            result = await get_client(env)
            assert result is mock_instance
            # verify_ssl should be False (not an SSLContext)
            verify = mock_client_cls.call_args[1].get("verify_ssl")
            assert verify is False


class TestGetSemaphore:
    @pytest.mark.asyncio
    async def test_existing_client(self):
        from src.core.utils.client_registry import get_semaphore, _registry
        from src.core.utils.client_registry import _ClientSlot
        from src.core.utils.async_network import AsyncAPIClient

        sem = asyncio.Semaphore(5)
        _registry["test-id"] = _ClientSlot(
            async_client=AsyncMock(spec=AsyncAPIClient),
            semaphore=sem,
            lock=asyncio.Lock(),
        )
        env = MagicMock()
        env.id = "test"
        env.base_url = "https://test.com"
        result = await get_semaphore(env)
        assert result is sem

    @pytest.mark.asyncio
    async def test_no_client_creates_one(self):
        from src.core.utils.client_registry import get_semaphore, _registry

        env = MagicMock()
        env.id = "env1"
        env.url = "https://test.com"
        env.username = "admin"
        env.password = "pass"

        with patch("src.core.utils.client_registry.get_client", new_callable=AsyncMock) as mock_get:
            mock_get.return_value = MagicMock()
            result = await get_semaphore(env)
            mock_get.assert_called_once()
            assert result is not None


class TestGetAuthLock:
    @pytest.mark.asyncio
    async def test_existing_client(self):
        from src.core.utils.client_registry import get_auth_lock, _registry
        from src.core.utils.client_registry import _ClientSlot
        from src.core.utils.async_network import AsyncAPIClient

        lock = asyncio.Lock()
        _registry["test-id"] = _ClientSlot(
            async_client=AsyncMock(spec=AsyncAPIClient),
            semaphore=asyncio.Semaphore(1),
            lock=lock,
        )
        env = MagicMock()
        env.id = "test"
        env.base_url = "https://test.com"
        result = await get_auth_lock(env)
        assert result is lock

    @pytest.mark.asyncio
    async def test_creates_client_if_missing(self):
        from src.core.utils.client_registry import get_auth_lock

        env = MagicMock()
        env.id = "env1"
        env.url = "https://test.com"
        env.username = "admin"
        env.password = "pass"

        with patch("src.core.utils.client_registry.get_client", new_callable=AsyncMock) as mock_get:
            mock_get.return_value = MagicMock()
            result = await get_auth_lock(env)
            assert result is not None


class TestShutdown:
    @pytest.mark.asyncio
    async def test_closes_all_clients(self):
        from src.core.utils.client_registry import shutdown, _registry
        from src.core.utils.client_registry import _ClientSlot
        from src.core.utils.async_network import AsyncAPIClient

        mock1 = AsyncMock(spec=AsyncAPIClient)
        mock2 = AsyncMock(spec=AsyncAPIClient)
        _registry["e1"] = _ClientSlot(
            async_client=mock1, semaphore=asyncio.Semaphore(1), lock=asyncio.Lock()
        )
        _registry["e2"] = _ClientSlot(
            async_client=mock2, semaphore=asyncio.Semaphore(1), lock=asyncio.Lock()
        )
        await shutdown()
        mock1.aclose.assert_awaited_once()
        mock2.aclose.assert_awaited_once()
        assert len(_registry) == 0

    @pytest.mark.asyncio
    async def test_handles_close_exception(self):
        from src.core.utils.client_registry import shutdown, _registry
        from src.core.utils.client_registry import _ClientSlot
        from src.core.utils.async_network import AsyncAPIClient

        mock = AsyncMock(spec=AsyncAPIClient)
        mock.aclose.side_effect = RuntimeError("close failed")
        _registry["e1"] = _ClientSlot(
            async_client=mock, semaphore=asyncio.Semaphore(1), lock=asyncio.Lock()
        )
        await shutdown()  # should not raise
        assert len(_registry) == 0
# #endregion Test.Core.ClientRegistry
