"""
Unit tests for identity/core/oauth_service.py

Tests OAuth service classes for Google and Microsoft integration.
"""
from unittest.mock import Mock, patch, MagicMock
import pytest


class TestGoogleOAuthServiceConstants:
    """Tests for Google OAuth constants."""

    def test_google_scopes_defined(self):
        """Should have GOOGLE_SCOPES constant."""
        from smoothschedule.identity.core.oauth_service import GOOGLE_SCOPES

        assert isinstance(GOOGLE_SCOPES, list)
        assert len(GOOGLE_SCOPES) > 0

    def test_google_scopes_include_gmail(self):
        """Should include Gmail access scope."""
        from smoothschedule.identity.core.oauth_service import GOOGLE_SCOPES

        has_mail_scope = any('mail.google.com' in scope for scope in GOOGLE_SCOPES)
        assert has_mail_scope


class TestMicrosoftOAuthServiceConstants:
    """Tests for Microsoft OAuth constants."""

    def test_microsoft_scopes_defined(self):
        """Should have MICROSOFT_SCOPES constant."""
        from smoothschedule.identity.core.oauth_service import MICROSOFT_SCOPES

        assert isinstance(MICROSOFT_SCOPES, list)
        assert len(MICROSOFT_SCOPES) > 0

    def test_microsoft_scopes_include_imap(self):
        """Should include IMAP access scope."""
        from smoothschedule.identity.core.oauth_service import MICROSOFT_SCOPES

        has_imap_scope = any('IMAP' in scope for scope in MICROSOFT_SCOPES)
        assert has_imap_scope

    def test_microsoft_scopes_include_smtp(self):
        """Should include SMTP access scope."""
        from smoothschedule.identity.core.oauth_service import MICROSOFT_SCOPES

        has_smtp_scope = any('SMTP' in scope for scope in MICROSOFT_SCOPES)
        assert has_smtp_scope

    def test_microsoft_scopes_include_offline_access(self):
        """Should include offline_access for refresh tokens."""
        from smoothschedule.identity.core.oauth_service import MICROSOFT_SCOPES

        assert 'offline_access' in MICROSOFT_SCOPES


class TestGoogleOAuthServiceClass:
    """Tests for GoogleOAuthService class."""

    def test_class_exists(self):
        """Should have GoogleOAuthService class."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        assert GoogleOAuthService is not None

    def test_init_reads_settings(self):
        """Should read client_id and client_secret from settings."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        with patch.object(__import__('django.conf', fromlist=['settings']).settings, 'GOOGLE_OAUTH_CLIENT_ID', 'test-id'):
            with patch.object(__import__('django.conf', fromlist=['settings']).settings, 'GOOGLE_OAUTH_CLIENT_SECRET', 'test-secret'):
                service = GoogleOAuthService()
                # Service should be initialized
                assert service is not None

    def test_is_configured_returns_false_when_missing_credentials(self):
        """Should return False when credentials not configured."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        service.client_id = ''
        service.client_secret = ''

        assert service.is_configured() is False

    def test_is_configured_returns_true_when_configured(self):
        """Should return True when credentials are configured."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        service.client_id = 'test-client-id'
        service.client_secret = 'test-client-secret'

        assert service.is_configured() is True

    def test_get_authorization_url_raises_when_not_configured(self):
        """Should raise ValueError when not configured."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        service.client_id = ''
        service.client_secret = ''

        with pytest.raises(ValueError) as exc_info:
            service.get_authorization_url('http://callback', 'state123')

        assert 'not configured' in str(exc_info.value)

    def test_has_get_authorization_url_method(self):
        """Should have get_authorization_url method."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        assert hasattr(service, 'get_authorization_url')
        assert callable(service.get_authorization_url)

    def test_has_exchange_code_for_tokens_method(self):
        """Should have exchange_code_for_tokens method."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        assert hasattr(service, 'exchange_code_for_tokens')
        assert callable(service.exchange_code_for_tokens)

    def test_has_refresh_token_method(self):
        """Should have refresh_token method."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        assert hasattr(service, 'refresh_token')
        assert callable(service.refresh_token)


class TestMicrosoftOAuthServiceClass:
    """Tests for MicrosoftOAuthService class."""

    def test_class_exists(self):
        """Should have MicrosoftOAuthService class."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        assert MicrosoftOAuthService is not None

    def test_is_configured_returns_false_when_missing_credentials(self):
        """Should return False when credentials not configured."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        service.client_id = ''
        service.client_secret = ''

        assert service.is_configured() is False

    def test_is_configured_returns_true_when_configured(self):
        """Should return True when credentials are configured."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        service.client_id = 'test-client-id'
        service.client_secret = 'test-client-secret'

        assert service.is_configured() is True

    def test_get_authorization_url_raises_when_not_configured(self):
        """Should raise ValueError when not configured."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        service.client_id = ''
        service.client_secret = ''

        with pytest.raises(ValueError) as exc_info:
            service.get_authorization_url('http://callback', 'state123')

        assert 'not configured' in str(exc_info.value)

    def test_has_get_authorization_url_method(self):
        """Should have get_authorization_url method."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        assert hasattr(service, 'get_authorization_url')
        assert callable(service.get_authorization_url)

    def test_has_exchange_code_for_tokens_method(self):
        """Should have exchange_code_for_tokens method."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        assert hasattr(service, 'exchange_code_for_tokens')
        assert callable(service.exchange_code_for_tokens)

    def test_has_refresh_token_method(self):
        """Should have refresh_token method."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        assert hasattr(service, 'refresh_token')
        assert callable(service.refresh_token)


class TestGetOAuthService:
    """Tests for get_oauth_service helper function."""

    def test_function_exists(self):
        """Should have get_oauth_service function."""
        from smoothschedule.identity.core.oauth_service import get_oauth_service

        assert callable(get_oauth_service)

    def test_returns_google_service_for_google(self):
        """Should return GoogleOAuthService for 'google' provider."""
        from smoothschedule.identity.core.oauth_service import (
            get_oauth_service,
            GoogleOAuthService
        )

        service = get_oauth_service('google')
        assert isinstance(service, GoogleOAuthService)

    def test_returns_microsoft_service_for_microsoft(self):
        """Should return MicrosoftOAuthService for 'microsoft' provider."""
        from smoothschedule.identity.core.oauth_service import (
            get_oauth_service,
            MicrosoftOAuthService
        )

        service = get_oauth_service('microsoft')
        assert isinstance(service, MicrosoftOAuthService)

    def test_raises_for_unknown_provider(self):
        """Should raise ValueError for unknown provider."""
        from smoothschedule.identity.core.oauth_service import get_oauth_service

        with pytest.raises(ValueError) as exc_info:
            get_oauth_service('unknown_provider')

        assert 'Unknown OAuth provider' in str(exc_info.value)


class TestOAuthTokenGeneration:
    """Tests for OAuth token generation utilities."""

    def test_generate_xoauth2_string_exists(self):
        """Should have generate_xoauth2_string function."""
        from smoothschedule.identity.core.oauth_service import generate_xoauth2_string

        assert callable(generate_xoauth2_string)

    def test_generate_xoauth2_string_format(self):
        """Should generate base64-encoded XOAUTH2 string."""
        from smoothschedule.identity.core.oauth_service import generate_xoauth2_string
        import base64

        result = generate_xoauth2_string('user@example.com', 'test-token')

        # Result should be base64-encoded
        assert isinstance(result, str)

        # Decode to verify contents
        decoded = base64.b64decode(result).decode('utf-8')
        assert 'user@example.com' in decoded
        assert 'Bearer' in decoded


class TestEnsureValidToken:
    """Tests for ensure_valid_token function."""

    def test_function_exists(self):
        """Should have ensure_valid_token function."""
        from smoothschedule.identity.core.oauth_service import ensure_valid_token

        assert callable(ensure_valid_token)

    def test_returns_none_when_credential_invalid(self):
        """Should return None when credential is marked invalid."""
        from smoothschedule.identity.core.oauth_service import ensure_valid_token

        mock_credential = Mock()
        mock_credential.is_valid = False
        mock_credential.id = 1

        result = ensure_valid_token(mock_credential)

        assert result is None

    def test_returns_access_token_when_not_expired(self):
        """Should return access token when not expired."""
        from smoothschedule.identity.core.oauth_service import ensure_valid_token

        mock_credential = Mock()
        mock_credential.is_valid = True
        mock_credential.needs_refresh.return_value = False
        mock_credential.access_token = 'valid-token-123'

        result = ensure_valid_token(mock_credential)

        assert result == 'valid-token-123'
        mock_credential.mark_used.assert_called_once()

    def test_refreshes_when_needed_google(self):
        """Should refresh Google token when needed."""
        from smoothschedule.identity.core.oauth_service import ensure_valid_token

        mock_credential = Mock()
        mock_credential.is_valid = True
        mock_credential.needs_refresh.return_value = True
        mock_credential.email = 'test@example.com'
        mock_credential.provider = 'google'
        mock_credential.access_token = 'new-token'
        mock_credential.refresh_token = 'refresh-token'

        with patch('smoothschedule.identity.core.oauth_service.GoogleOAuthService') as mock_service:
            mock_instance = Mock()
            mock_instance.refresh_token.return_value = True
            mock_service.return_value = mock_instance

            result = ensure_valid_token(mock_credential)

            assert result == 'new-token'
            mock_credential.mark_used.assert_called_once()

    def test_refreshes_when_needed_microsoft(self):
        """Should refresh Microsoft token when needed."""
        from smoothschedule.identity.core.oauth_service import ensure_valid_token

        mock_credential = Mock()
        mock_credential.is_valid = True
        mock_credential.needs_refresh.return_value = True
        mock_credential.email = 'test@example.com'
        mock_credential.provider = 'microsoft'
        mock_credential.access_token = 'new-token'
        mock_credential.refresh_token = 'refresh-token'

        with patch('smoothschedule.identity.core.oauth_service.MicrosoftOAuthService') as mock_service:
            mock_instance = Mock()
            mock_instance.refresh_token.return_value = True
            mock_service.return_value = mock_instance

            result = ensure_valid_token(mock_credential)

            assert result == 'new-token'
            mock_credential.mark_used.assert_called_once()

    def test_returns_none_when_refresh_fails(self):
        """Should return None when token refresh fails."""
        from smoothschedule.identity.core.oauth_service import ensure_valid_token

        mock_credential = Mock()
        mock_credential.is_valid = True
        mock_credential.needs_refresh.return_value = True
        mock_credential.email = 'test@example.com'
        mock_credential.provider = 'google'

        with patch('smoothschedule.identity.core.oauth_service.GoogleOAuthService') as mock_service:
            mock_instance = Mock()
            mock_instance.refresh_token.return_value = False
            mock_service.return_value = mock_instance

            result = ensure_valid_token(mock_credential)

            assert result is None


class TestMicrosoftOAuthServiceAuthority:
    """Tests for MicrosoftOAuthService authority property."""

    def test_authority_uses_tenant_id(self):
        """Should construct authority URL using tenant_id."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        service.tenant_id = 'custom-tenant'

        assert 'custom-tenant' in service.authority
        assert 'login.microsoftonline.com' in service.authority

    def test_default_tenant_is_common(self):
        """Should default to 'common' tenant."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()

        assert 'common' in service.authority or service.tenant_id == 'common'


class TestGoogleOAuthServiceRefreshToken:
    """Tests for GoogleOAuthService.refresh_token method."""

    def test_returns_false_when_no_refresh_token(self):
        """Should return False when credential has no refresh token."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        service.client_id = 'test-id'
        service.client_secret = 'test-secret'

        mock_credential = Mock()
        mock_credential.refresh_token = None
        mock_credential.id = 1

        result = service.refresh_token(mock_credential)

        assert result is False
        mock_credential.mark_invalid.assert_called_once()


class TestMicrosoftOAuthServiceRefreshToken:
    """Tests for MicrosoftOAuthService.refresh_token method."""

    def test_returns_false_when_no_refresh_token(self):
        """Should return False when credential has no refresh token."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        service.client_id = 'test-id'
        service.client_secret = 'test-secret'

        mock_credential = Mock()
        mock_credential.refresh_token = None
        mock_credential.id = 1

        result = service.refresh_token(mock_credential)

        assert result is False
        mock_credential.mark_invalid.assert_called_once()


class TestGoogleOAuthServiceGetAuthorizationUrl:
    """Tests for GoogleOAuthService.get_authorization_url method."""

    @patch('google_auth_oauthlib.flow.Flow.from_client_config')
    def test_returns_url_when_configured(self, mock_flow):
        """Should return authorization URL when configured."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        mock_flow_instance = Mock()
        mock_flow_instance.authorization_url.return_value = ('https://auth.url', 'state')
        mock_flow.return_value = mock_flow_instance

        service = GoogleOAuthService()
        service.client_id = 'test-id'
        service.client_secret = 'test-secret'

        result = service.get_authorization_url('http://callback', 'state123')

        assert result == 'https://auth.url'
        mock_flow.assert_called_once()


class TestMicrosoftOAuthServiceGetAuthorizationUrl:
    """Tests for MicrosoftOAuthService.get_authorization_url method."""

    @patch('msal.ConfidentialClientApplication')
    def test_returns_url_when_configured(self, mock_msal):
        """Should return authorization URL when configured."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        mock_app = Mock()
        mock_app.get_authorization_request_url.return_value = 'https://microsoft.auth.url'
        mock_msal.return_value = mock_app

        service = MicrosoftOAuthService()
        service.client_id = 'test-id'
        service.client_secret = 'test-secret'

        result = service.get_authorization_url('http://callback', 'state123')

        assert result == 'https://microsoft.auth.url'


class TestGoogleOAuthServiceExchangeCodeForTokens:
    """Tests for GoogleOAuthService.exchange_code_for_tokens method."""

    def test_raises_when_not_configured(self):
        """Should raise ValueError when not configured."""
        from smoothschedule.identity.core.oauth_service import GoogleOAuthService

        service = GoogleOAuthService()
        service.client_id = ''
        service.client_secret = ''

        with pytest.raises(ValueError) as exc_info:
            service.exchange_code_for_tokens('auth-code', 'http://callback')

        assert 'not configured' in str(exc_info.value)


class TestMicrosoftOAuthServiceExchangeCodeForTokens:
    """Tests for MicrosoftOAuthService.exchange_code_for_tokens method."""

    def test_raises_when_not_configured(self):
        """Should raise ValueError when not configured."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        service = MicrosoftOAuthService()
        service.client_id = ''
        service.client_secret = ''

        with pytest.raises(ValueError) as exc_info:
            service.exchange_code_for_tokens('auth-code', 'http://callback')

        assert 'not configured' in str(exc_info.value)

    @patch('msal.ConfidentialClientApplication')
    def test_raises_on_token_error(self, mock_msal):
        """Should raise ValueError when token exchange fails."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        mock_app = Mock()
        mock_app.acquire_token_by_authorization_code.return_value = {
            'error': 'invalid_grant',
            'error_description': 'The authorization code has expired'
        }
        mock_msal.return_value = mock_app

        service = MicrosoftOAuthService()
        service.client_id = 'test-id'
        service.client_secret = 'test-secret'

        with pytest.raises(ValueError) as exc_info:
            service.exchange_code_for_tokens('auth-code', 'http://callback')

        assert 'Token exchange failed' in str(exc_info.value)

    @patch('msal.ConfidentialClientApplication')
    def test_extracts_email_from_id_token(self, mock_msal):
        """Should extract email from id_token_claims."""
        from smoothschedule.identity.core.oauth_service import MicrosoftOAuthService

        mock_app = Mock()
        mock_app.acquire_token_by_authorization_code.return_value = {
            'access_token': 'access-123',
            'refresh_token': 'refresh-456',
            'expires_in': 3600,
            'id_token_claims': {'preferred_username': 'user@outlook.com'},
            'scope': 'openid email'
        }
        mock_msal.return_value = mock_app

        service = MicrosoftOAuthService()
        service.client_id = 'test-id'
        service.client_secret = 'test-secret'

        result = service.exchange_code_for_tokens('auth-code', 'http://callback')

        assert result['email'] == 'user@outlook.com'
        assert result['access_token'] == 'access-123'
        assert result['refresh_token'] == 'refresh-456'
