"""Unit tests for authentication providers."""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from azure_discovery.auth import AzureADAuthProvider, APIKeyAuthProvider
from azure_discovery.auth.models import AuthenticationError


class TestAPIKeyAuthProvider:
    """Tests for API key authentication."""
    
    def test_hash_key(self):
        """Test key hashing is consistent."""
        key = "test-key-12345"
        hash1 = APIKeyAuthProvider._hash_key(key)
        hash2 = APIKeyAuthProvider._hash_key(key)
        
        assert hash1 == hash2
        assert len(hash1) == 64  # SHA-256 hex digest
    
    def test_generate_key(self):
        """Test key generation produces unique keys."""
        key1 = APIKeyAuthProvider.generate_key()
        key2 = APIKeyAuthProvider.generate_key()
        
        assert key1 != key2
        assert len(key1) > 32
    
    @pytest.mark.asyncio
    async def test_authenticate_valid_key(self):
        """Test successful authentication with valid key."""
        key = "test-key-12345"
        key_hash = APIKeyAuthProvider._hash_key(key)
        
        provider = APIKeyAuthProvider(
            keys={
                key_hash: {
                    "id": "test-001",
                    "name": "Test Key",
                    "scopes": ["Discovery.Read"],
                    "roles": ["Reader"],
                }
            }
        )
        
        result = await provider.authenticate(key)
        
        assert result["id"] == "test-001"
        assert result["name"] == "Test Key"
        assert result["scopes"] == ["Discovery.Read"]
        assert result["source"] == "api_key"
    
    @pytest.mark.asyncio
    async def test_authenticate_invalid_key(self):
        """Test authentication fails with invalid key."""
        provider = APIKeyAuthProvider(keys={})
        
        with pytest.raises(AuthenticationError, match="Invalid API key"):
            await provider.authenticate("invalid-key")
    
    @pytest.mark.asyncio
    async def test_authenticate_revoked_key(self):
        """Test authentication fails with revoked key."""
        key = "revoked-key"
        key_hash = APIKeyAuthProvider._hash_key(key)
        
        provider = APIKeyAuthProvider(
            keys={
                key_hash: {
                    "id": "revoked-001",
                    "name": "Revoked Key",
                    "revoked": True,
                }
            }
        )
        
        with pytest.raises(AuthenticationError, match="revoked"):
            await provider.authenticate(key)


class TestAzureADAuthProvider:
    """Tests for Azure AD authentication."""
    
    @pytest.mark.asyncio
    async def test_authenticate_valid_token(self):
        """Test successful authentication with valid JWT."""
        provider = AzureADAuthProvider(
            tenant_id="test-tenant",
            client_id="test-client",
            audience="api://test",
        )
        
        # Mock the JWT validation
        mock_payload = {
            "oid": "user-123",
            "upn": "user@example.com",
            "name": "Test User",
            "roles": ["Discovery.Reader"],
            "scp": "Discovery.Read",
            "tid": "test-tenant",
        }
        
        with patch('jwt.decode', return_value=mock_payload):
            with patch.object(provider, 'jwk_client') as mock_jwk:
                mock_key = MagicMock()
                mock_key.key = "test-key"
                mock_jwk.get_signing_key_from_jwt.return_value = mock_key
                
                result = await provider.authenticate("fake-jwt-token")
        
        assert result["id"] == "user-123"
        assert result["email"] == "user@example.com"
        assert result["name"] == "Test User"
        assert result["roles"] == ["Discovery.Reader"]
        assert result["scopes"] == ["Discovery.Read"]
        assert result["source"] == "azure_ad"
    
    @pytest.mark.asyncio
    async def test_authenticate_expired_token(self):
        """Test authentication fails with expired token."""
        import jwt as jwt_module
        
        provider = AzureADAuthProvider(
            tenant_id="test-tenant",
            client_id="test-client",
            audience="api://test",
        )
        
        with patch('jwt.decode', side_effect=jwt_module.ExpiredSignatureError()):
            with patch.object(provider, 'jwk_client') as mock_jwk:
                mock_key = MagicMock()
                mock_key.key = "test-key"
                mock_jwk.get_signing_key_from_jwt.return_value = mock_key
                
                with pytest.raises(AuthenticationError, match="expired"):
                    await provider.authenticate("expired-token")
    
    @pytest.mark.asyncio
    async def test_authenticate_wrong_audience(self):
        """Test authentication fails with wrong audience."""
        import jwt as jwt_module
        
        provider = AzureADAuthProvider(
            tenant_id="test-tenant",
            client_id="test-client",
            audience="api://test",
        )
        
        with patch('jwt.decode', side_effect=jwt_module.InvalidAudienceError()):
            with patch.object(provider, 'jwk_client') as mock_jwk:
                mock_key = MagicMock()
                mock_key.key = "test-key"
                mock_jwk.get_signing_key_from_jwt.return_value = mock_key
                
                with pytest.raises(AuthenticationError, match="Invalid audience"):
                    await provider.authenticate("wrong-audience-token")
