"""Tests for the AuthProvider protocol and the DI factory (U11)."""

from __future__ import annotations

import pytest

from agentkit.server.auth.providers import (
    AuthProvider,
    LocalAuthProvider,
    StubOIDCProvider,
    get_auth_provider,
    reset_auth_provider,
)
from agentkit.server.auth.providers.base import AuthProvider as AuthProviderFromBase
from agentkit.server.auth.providers.exceptions import (
    AuthProviderError,
    InvalidCredentials,
    ProviderNotImplemented,
)


class TestProtocolConformance:
    """The runtime_checkable Protocol accepts both real implementations."""

    def test_local_passes_isinstance_check(self):
        provider = LocalAuthProvider()
        assert isinstance(provider, AuthProvider)
        assert isinstance(provider, AuthProviderFromBase)

    def test_stub_passes_isinstance_check(self):
        provider = StubOIDCProvider()
        assert isinstance(provider, AuthProvider)
        assert isinstance(provider, AuthProviderFromBase)


class TestProviderNames:
    def test_local_provider_name(self):
        assert LocalAuthProvider.name == "local"

    def test_stub_oidc_provider_name(self):
        assert StubOIDCProvider.name == "oidc-stub"


class TestDiFactory:
    """``get_auth_provider`` is a memoized singleton, env-driven."""

    def setup_method(self):
        """Reset cache and env before each test."""
        reset_auth_provider()
        self._saved_env = {}
        for key in ("AGENTKIT_AUTH_PROVIDER",):
            self._saved_env[key] = __import__("os").environ.pop(key, None)

    def teardown_method(self):
        for key, value in self._saved_env.items():
            if value is not None:
                __import__("os").environ[key] = value
        reset_auth_provider()

    def test_default_provider_is_local(self):
        provider = get_auth_provider()
        assert isinstance(provider, LocalAuthProvider)
        assert provider.name == "local"

    def test_oidc_stub_provider(self, monkeypatch):
        monkeypatch.setenv("AGENTKIT_AUTH_PROVIDER", "oidc-stub")
        reset_auth_provider()
        provider = get_auth_provider()
        assert isinstance(provider, StubOIDCProvider)
        assert provider.name == "oidc-stub"

    def test_unknown_provider_raises_value_error(self, monkeypatch):
        monkeypatch.setenv("AGENTKIT_AUTH_PROVIDER", "ldap-unknown")
        reset_auth_provider()
        with pytest.raises(ValueError, match="unknown auth provider"):
            get_auth_provider()

    def test_factory_is_memoized(self):
        first = get_auth_provider()
        second = get_auth_provider()
        assert first is second

    def test_reset_clears_cache(self, monkeypatch):
        first = get_auth_provider()
        reset_auth_provider()
        monkeypatch.setenv("AGENTKIT_AUTH_PROVIDER", "oidc-stub")
        second = get_auth_provider()
        assert first is not second
        assert isinstance(second, StubOIDCProvider)


class TestExceptionHierarchy:
    def test_invalid_credentials_inherits_auth_provider_error(self):
        assert issubclass(InvalidCredentials, AuthProviderError)
        assert issubclass(InvalidCredentials, Exception)

    def test_provider_not_implemented_inherits_auth_provider_error(self):
        assert issubclass(ProviderNotImplemented, AuthProviderError)
        assert issubclass(ProviderNotImplemented, Exception)

    def test_invalid_credentials_can_be_raised_and_caught(self):
        with pytest.raises(InvalidCredentials):
            raise InvalidCredentials("test message")

    def test_invalid_credentials_caught_as_base(self):
        """Route layer catches AuthProviderError to handle all provider errors."""
        with pytest.raises(AuthProviderError):
            raise InvalidCredentials("test")

    def test_provider_not_implemented_caught_as_base(self):
        with pytest.raises(AuthProviderError):
            raise ProviderNotImplemented("test")
