"""Tests for ai4gh.core.auth."""

from __future__ import annotations

import pytest

from ai4gh.core.auth import (
    BearerTokenAuth,
    NoAuth,
    OAuth2Auth,
    PassportAuth,
    auto_detect_auth,
)
from ai4gh.core.config import AuthConfig
from ai4gh.core.exceptions import AuthError


class TestNoAuth:
    @pytest.mark.asyncio
    async def test_returns_empty_headers(self) -> None:
        auth = NoAuth()
        headers = await auth.get_headers()
        assert headers == {}


class TestBearerTokenAuth:
    @pytest.mark.asyncio
    async def test_returns_bearer_header(self) -> None:
        auth = BearerTokenAuth("my-token-123")
        headers = await auth.get_headers()
        assert headers == {"Authorization": "Bearer my-token-123"}


class TestOAuth2Auth:
    def test_init(self) -> None:
        auth = OAuth2Auth(
            token_url="https://auth.example.org/token",
            client_id="client-1",
            client_secret="secret-1",
        )
        assert auth._token_url == "https://auth.example.org/token"
        assert auth._access_token is None

    @pytest.mark.asyncio
    async def test_get_headers_fetches_token(self, httpx_mock) -> None:

        httpx_mock.add_response(
            url="https://auth.example.org/token",
            json={"access_token": "fetched-token-abc"},
        )
        auth = OAuth2Auth(
            token_url="https://auth.example.org/token",
            client_id="client-1",
            client_secret="secret-1",
        )
        headers = await auth.get_headers()
        assert headers == {"Authorization": "Bearer fetched-token-abc"}

    @pytest.mark.asyncio
    async def test_caches_token(self, httpx_mock) -> None:
        httpx_mock.add_response(
            url="https://auth.example.org/token",
            json={"access_token": "cached-token"},
        )
        auth = OAuth2Auth(
            token_url="https://auth.example.org/token",
            client_id="c",
            client_secret="s",
        )
        await auth.get_headers()
        # second call should not make another HTTP request
        headers = await auth.get_headers()
        assert headers == {"Authorization": "Bearer cached-token"}

    def test_invalidate(self) -> None:
        auth = OAuth2Auth(
            token_url="https://auth.example.org/token",
            client_id="c",
            client_secret="s",
        )
        auth._access_token = "old-token"
        auth.invalidate()
        assert auth._access_token is None

    @pytest.mark.asyncio
    async def test_fetch_failure_raises_auth_error(self, httpx_mock) -> None:
        httpx_mock.add_response(
            url="https://auth.example.org/token",
            status_code=401,
            text="unauthorized",
        )
        auth = OAuth2Auth(
            token_url="https://auth.example.org/token",
            client_id="c",
            client_secret="s",
        )
        with pytest.raises(AuthError, match="OAuth2 token fetch failed"):
            await auth.get_headers()


class TestPassportAuth:
    # Create a minimal JWT for testing (unsigned / no-verify)
    @pytest.fixture
    def sample_passport_token(self) -> str:
        """Create a minimal unsigned JWT for testing."""
        import base64
        import json

        header = base64.urlsafe_b64encode(
            json.dumps({"alg": "none", "typ": "JWT"}).encode()
        ).rstrip(b"=")
        payload = base64.urlsafe_b64encode(
            json.dumps(
                {
                    "sub": "user-123",
                    "ga4gh_passport_v1": [
                        {"type": "ControlledAccessGrants", "value": "dataset-1"},
                        {"type": "ResearcherStatus", "value": "approved"},
                    ],
                }
            ).encode()
        ).rstrip(b"=")
        return f"{header.decode()}.{payload.decode()}."

    @pytest.mark.asyncio
    async def test_get_headers(self, sample_passport_token: str) -> None:
        auth = PassportAuth(sample_passport_token, verify=False)
        headers = await auth.get_headers()
        assert headers["Authorization"].startswith("Bearer ")

    def test_decode_no_verify(self, sample_passport_token: str) -> None:
        auth = PassportAuth(sample_passport_token, verify=False)
        decoded = auth.decode(verify=False)
        assert decoded["sub"] == "user-123"

    def test_get_visas(self, sample_passport_token: str) -> None:
        auth = PassportAuth(sample_passport_token, verify=False)
        visas = auth.get_visas()
        assert len(visas) == 2
        assert visas[0]["type"] == "ControlledAccessGrants"

    def test_has_visa_type(self, sample_passport_token: str) -> None:
        auth = PassportAuth(sample_passport_token, verify=False)
        assert auth.has_visa_type("ControlledAccessGrants") is True
        assert auth.has_visa_type("LinkedIdentities") is False

    def test_decode_invalid_token(self) -> None:
        auth = PassportAuth("not-a-jwt", verify=False)
        with pytest.raises(AuthError, match="Invalid Passport token"):
            auth.decode(verify=True)


class TestAutoDetectAuth:
    def test_bearer_token(self) -> None:
        config = AuthConfig(token="my-token")
        auth = auto_detect_auth(config)
        assert isinstance(auth, BearerTokenAuth)

    def test_oauth2(self) -> None:
        config = AuthConfig(
            token_url="https://auth.example.org/token",
            client_id="client-1",
            client_secret="secret-1",
        )
        auth = auto_detect_auth(config)
        assert isinstance(auth, OAuth2Auth)

    def test_no_auth(self) -> None:
        config = AuthConfig()
        auth = auto_detect_auth(config)
        assert isinstance(auth, NoAuth)

    def test_token_takes_precedence(self) -> None:
        config = AuthConfig(
            token="my-token",
            token_url="https://auth.example.org/token",
            client_id="c",
            client_secret="s",
        )
        auth = auto_detect_auth(config)
        assert isinstance(auth, BearerTokenAuth)
