"""Tests for OAuth 2.0 shared code."""

import pytest
from pydantic import ValidationError

from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthMetadata


class TestOAuthMetadata:
    """Tests for OAuthMetadata parsing."""

    def test_oauth(self):
        """Should not throw when parsing OAuth metadata."""
        OAuthMetadata.model_validate(
            {
                "issuer": "https://example.com",
                "authorization_endpoint": "https://example.com/oauth2/authorize",
                "token_endpoint": "https://example.com/oauth2/token",
                "scopes_supported": ["read", "write"],
                "response_types_supported": ["code", "token"],
                "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
            }
        )

    def test_oidc(self):
        """Should not throw when parsing OIDC metadata."""
        OAuthMetadata.model_validate(
            {
                "issuer": "https://example.com",
                "authorization_endpoint": "https://example.com/oauth2/authorize",
                "token_endpoint": "https://example.com/oauth2/token",
                "end_session_endpoint": "https://example.com/logout",
                "id_token_signing_alg_values_supported": ["RS256"],
                "jwks_uri": "https://example.com/.well-known/jwks.json",
                "response_types_supported": ["code", "token"],
                "revocation_endpoint": "https://example.com/oauth2/revoke",
                "scopes_supported": ["openid", "read", "write"],
                "subject_types_supported": ["public"],
                "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
                "userinfo_endpoint": "https://example.com/oauth2/userInfo",
            }
        )

    def test_oauth_with_jarm(self):
        """Should not throw when parsing OAuth metadata that includes JARM response modes."""
        OAuthMetadata.model_validate(
            {
                "issuer": "https://example.com",
                "authorization_endpoint": "https://example.com/oauth2/authorize",
                "token_endpoint": "https://example.com/oauth2/token",
                "scopes_supported": ["read", "write"],
                "response_types_supported": ["code", "token"],
                "response_modes_supported": [
                    "query",
                    "fragment",
                    "form_post",
                    "query.jwt",
                    "fragment.jwt",
                    "form_post.jwt",
                    "jwt",
                ],
                "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post"],
            }
        )


# RFC 7591 §2 marks client_uri/logo_uri/tos_uri/policy_uri/jwks_uri as OPTIONAL.
# Some authorization servers echo the client's omitted metadata back as ""
# instead of dropping the keys; without coercion, AnyHttpUrl rejects "" and
# the whole registration response is thrown away even though the server
# returned a valid client_id.


@pytest.mark.parametrize(
    "empty_field",
    ["client_uri", "logo_uri", "tos_uri", "policy_uri", "jwks_uri"],
)
def test_optional_url_empty_string_coerced_to_none(empty_field: str):
    data = {
        "redirect_uris": ["https://example.com/callback"],
        empty_field: "",
    }
    metadata = OAuthClientMetadata.model_validate(data)
    assert getattr(metadata, empty_field) is None


def test_all_optional_urls_empty_together():
    data = {
        "redirect_uris": ["https://example.com/callback"],
        "client_uri": "",
        "logo_uri": "",
        "tos_uri": "",
        "policy_uri": "",
        "jwks_uri": "",
    }
    metadata = OAuthClientMetadata.model_validate(data)
    assert metadata.client_uri is None
    assert metadata.logo_uri is None
    assert metadata.tos_uri is None
    assert metadata.policy_uri is None
    assert metadata.jwks_uri is None


def test_valid_url_passes_through_unchanged():
    data = {
        "redirect_uris": ["https://example.com/callback"],
        "client_uri": "https://udemy.com/",
    }
    metadata = OAuthClientMetadata.model_validate(data)
    assert str(metadata.client_uri) == "https://udemy.com/"


def test_information_full_inherits_coercion():
    """OAuthClientInformationFull subclasses OAuthClientMetadata, so the
    same coercion applies to DCR responses parsed via the full model."""
    data = {
        "client_id": "abc123",
        "redirect_uris": ["https://example.com/callback"],
        "client_uri": "",
        "logo_uri": "",
        "tos_uri": "",
        "policy_uri": "",
        "jwks_uri": "",
    }
    info = OAuthClientInformationFull.model_validate(data)
    assert info.client_id == "abc123"
    assert info.client_uri is None
    assert info.logo_uri is None
    assert info.tos_uri is None
    assert info.policy_uri is None
    assert info.jwks_uri is None


def test_invalid_non_empty_url_still_rejected():
    """Coercion must only touch empty strings — garbage URLs still raise."""
    data = {
        "redirect_uris": ["https://example.com/callback"],
        "client_uri": "not a url",
    }
    with pytest.raises(ValidationError):
        OAuthClientMetadata.model_validate(data)
