"""Tests for Google OAuth service.

Tests the Google OAuth flow with mocked HTTP responses using respx.
"""

from unittest.mock import patch

import pytest
import respx
from httpx import Response

from app.services.oauth.google import GoogleOAuth, GoogleOAuthError

pytestmark = pytest.mark.asyncio


class TestGetAuthorizationUrl:
    """Tests for get_authorization_url method."""

    def test_raises_when_not_configured(self):
        """Test that get_authorization_url raises when Google OAuth is not configured.

        Without client ID, the method should raise GoogleOAuthError.
        """
        oauth = GoogleOAuth()

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = None

            with pytest.raises(GoogleOAuthError, match="not configured"):
                oauth.get_authorization_url("http://localhost/callback", "state123")

    def test_returns_valid_url_when_configured(self):
        """Test that get_authorization_url returns properly formatted URL.

        The URL should include client_id, redirect_uri, state, and scopes.
        """
        oauth = GoogleOAuth()

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = "test-client-id"
            mock_settings.google_client_secret = "test-secret"

            url = oauth.get_authorization_url("http://localhost/callback", "state123")

            assert "accounts.google.com/o/oauth2/v2/auth" in url
            assert "client_id=test-client-id" in url
            assert "redirect_uri=http" in url
            assert "state=state123" in url
            assert "scope=" in url
            assert "response_type=code" in url


class TestExchangeCodeForTokens:
    """Tests for exchange_code_for_tokens method."""

    @respx.mock
    async def test_returns_tokens_on_success(self):
        """Test that exchange_code_for_tokens returns tokens on success.

        Mocks Google's token endpoint to return valid tokens.
        """
        oauth = GoogleOAuth()

        respx.post("https://oauth2.googleapis.com/token").mock(
            return_value=Response(
                200,
                json={
                    "access_token": "test-access-token",
                    "id_token": "test-id-token",
                    "expires_in": 3600,
                    "token_type": "Bearer",
                },
            )
        )

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = "test-client-id"
            mock_settings.google_client_secret.get_secret_value.return_value = "test-secret"

            tokens = await oauth.exchange_code_for_tokens("auth-code", "http://localhost/callback")

            assert tokens["access_token"] == "test-access-token"

    @respx.mock
    async def test_raises_on_error_response(self):
        """Test that exchange_code_for_tokens raises on error from Google.

        If Google returns an error, GoogleOAuthError should be raised.
        """
        oauth = GoogleOAuth()

        respx.post("https://oauth2.googleapis.com/token").mock(
            return_value=Response(
                400,
                json={
                    "error": "invalid_grant",
                    "error_description": "Code has expired",
                },
            )
        )

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = "test-client-id"
            mock_settings.google_client_secret.get_secret_value.return_value = "test-secret"

            with pytest.raises(GoogleOAuthError, match="Token exchange failed"):
                await oauth.exchange_code_for_tokens("expired-code", "http://localhost/callback")


class TestFetchUserInfo:
    """Tests for fetch_user_info method."""

    @respx.mock
    async def test_returns_user_info_on_success(self):
        """Test that fetch_user_info returns user data from Google.

        Mocks Google's userinfo endpoint.
        """
        oauth = GoogleOAuth()

        respx.get("https://www.googleapis.com/oauth2/v2/userinfo").mock(
            return_value=Response(
                200,
                json={
                    "id": "google-user-123",
                    "email": "user@gmail.com",
                    "name": "Test User",
                    "picture": "https://google.com/avatar.jpg",
                },
            )
        )

        user_info = await oauth.fetch_user_info("test-access-token")

        assert user_info["id"] == "google-user-123"
        assert user_info["email"] == "user@gmail.com"

    @respx.mock
    async def test_raises_on_error_response(self):
        """Test that fetch_user_info raises on error from Google."""
        oauth = GoogleOAuth()

        respx.get("https://www.googleapis.com/oauth2/v2/userinfo").mock(
            return_value=Response(401, json={"error": "Invalid token"})
        )

        with pytest.raises(GoogleOAuthError, match="Failed to fetch user info"):
            await oauth.fetch_user_info("invalid-token")


class TestGetUserInfo:
    """Tests for get_user_info method (full flow)."""

    @respx.mock
    async def test_returns_oauth_user_info_on_success(self):
        """Test that get_user_info completes full OAuth flow.

        This tests the combined token exchange + user info fetch.
        """
        oauth = GoogleOAuth()

        # Mock token exchange
        respx.post("https://oauth2.googleapis.com/token").mock(
            return_value=Response(
                200,
                json={
                    "access_token": "test-access-token",
                    "id_token": "test-id-token",
                    "expires_in": 3600,
                },
            )
        )

        # Mock user info
        respx.get("https://www.googleapis.com/oauth2/v2/userinfo").mock(
            return_value=Response(
                200,
                json={
                    "id": "google-user-456",
                    "email": "fullflow@gmail.com",
                    "name": "Full Flow User",
                    "picture": "https://google.com/fullflow.jpg",
                },
            )
        )

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = "test-client-id"
            mock_settings.google_client_secret.get_secret_value.return_value = "test-secret"

            result = await oauth.get_user_info("auth-code", "http://localhost/callback")

            assert result.provider == "google"
            assert result.oauth_id == "google-user-456"
            assert result.email == "fullflow@gmail.com"
            assert result.name == "Full Flow User"
            assert result.avatar_url == "https://google.com/fullflow.jpg"

    @respx.mock
    async def test_raises_when_no_access_token(self):
        """Test that get_user_info raises when token response lacks access_token."""
        oauth = GoogleOAuth()

        respx.post("https://oauth2.googleapis.com/token").mock(
            return_value=Response(
                200,
                json={"id_token": "only-id-token"},  # No access_token
            )
        )

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = "test-client-id"
            mock_settings.google_client_secret.get_secret_value.return_value = "test-secret"

            with pytest.raises(GoogleOAuthError, match="No access token"):
                await oauth.get_user_info("auth-code", "http://localhost/callback")


class TestIsConfigured:
    """Tests for is_configured method."""

    def test_returns_false_when_not_configured(self):
        """Test that is_configured returns False without credentials."""
        oauth = GoogleOAuth()

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = None
            mock_settings.google_client_secret = None

            assert oauth.is_configured() is False

    def test_returns_true_when_configured(self):
        """Test that is_configured returns True with credentials."""
        oauth = GoogleOAuth()

        with patch("app.services.oauth.google.settings") as mock_settings:
            mock_settings.google_client_id = "client-id"
            mock_settings.google_client_secret = "client-secret"

            assert oauth.is_configured() is True
