"""aiohttp adapter. Use either the helper (explicit, inside a handler) or the
decorator (resolves keys/oidc from app config). Both deny fail-closed with the
core's status/reason.

    # at app build:
    app["andrea_svc_keys"] = SVC_KEYS          # str | {kid: secret}
    app["andrea_oidc"] = OIDCValidator(...)     # optional, enables the user path

    @require_auth()
    async def post_event(request): ...
"""
from __future__ import annotations

from functools import wraps
from typing import Mapping, Optional, Union

from .core import AuthResult, DEFAULT_API_KEY_HEADER, OIDCValidator, authenticate

APP_KEYS = "andrea_svc_keys"
APP_OIDC = "andrea_oidc"


def check_request(request, *, service_keys=None, api_key_header=DEFAULT_API_KEY_HEADER,
                  oidc: Optional[OIDCValidator] = None) -> AuthResult:
    """Classify an aiohttp request. Falls back to app config when args omitted."""
    sk = service_keys if service_keys is not None else request.app.get(APP_KEYS)
    oi = oidc if oidc is not None else request.app.get(APP_OIDC)
    return authenticate(lambda h: request.headers.get(h, ""),
                        service_keys=sk, api_key_header=api_key_header, oidc=oi)


def require_auth(*, service_keys: Union[str, Mapping[str, str], None] = None,
                 api_key_header: str = DEFAULT_API_KEY_HEADER,
                 oidc: Optional[OIDCValidator] = None):
    """Decorator for aiohttp handlers. On success sets request['principal']."""
    def deco(handler):
        @wraps(handler)
        async def wrapped(request):
            from aiohttp import web  # lazy
            res = check_request(request, service_keys=service_keys,
                                api_key_header=api_key_header, oidc=oidc)
            if not res.ok:
                return web.json_response(
                    {"error": "unauthorized", "reason": res.reason}, status=res.status)
            request["principal"] = res.principal
            return await handler(request)
        return wrapped
    return deco
