# Copyright (C) 2023 Cochise Ruhulessin
#
# All rights reserved. No warranty, explicit or implicit, provided. In
# no event shall the author(s) be liable for any claim or damages.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
import logging

import fastapi
from headless.ext.oauth2 import Client
from headless.ext.oauth2 import Error
from headless.ext.oauth2 import models

from cbra.types import Request
from cbra.ext.oauth2.types import FatalAuthorizationException
from ..types import FrontendException
from .applicationclient import ApplicationClient
from .frontendredirecturi import FrontendRedirectURI


__all__: list[str] = [
    'FrontendTokenResponse'
]


logger: logging.Logger = logging.getLogger('cbra.endpoint')


async def get(
    request: Request,
    client: Client = ApplicationClient,
    redirect_uri: str = FrontendRedirectURI,
    expected_nonce: str = fastapi.Cookie(
        default=...,
        title="Nonce",
        description=(
            "The expected `nonce` claim in the ID Token."
        ),
        alias='bff.nonce'
    ),
    expected_state: str | None = fastapi.Cookie(
        default=None,
        title="Expected state",
        description=(
            "The expected value of the `state` query parameter."
        ),
        alias='bff.state'
    ),
    code: str | None = fastapi.Query(
        default=None,
        title='Authorization code',
        description=(
            'The authorization code generated by the authorization server. '
            'Required if using the Authorization Code Flow and the response '
            'mode is `query`, otherwise this parameter is ignored.'
        )
    ),
    state: str | None = fastapi.Query(
        default=None,
        title='State',
        description=(
            'An opaque value used by the client to maintain state '
            'between the request and callback. The authorization '
            'server includes this value when redirecting the '
            'user-agent back to the client. If supplied, this '
            'parameter **must** equal the `state` parameter used when '
            'creating the authorization request. The `state` parameter '
            'is ignored when using JARM because it is included in the '
            'JSON Web Token supplied using the `jwt` parameter per '
            'chosen response mode.'
        )
    ),
    iss: str | None = fastapi.Query(
        default=None,
        title='Issuer',
        description=(
            'Identifies the authorization server that redirected to '
            'this endpoint, as defined in RFC 9207. If the client supports '
            'OAuth 2.0 Authorization Server Issuer Identification, then '
            'this parameter is **required**, if the response mode is not '
            'JWT Secured Authorization Response Mode (JARM) per RFC 9101.\n\n'
            'The `iss` parameter value is the issuer identifier of '
            'the authorization server that created the authorization '
            'response, as defined in RFC 8414.  Its value **must** '
            'be a URL that uses the `https` scheme without any '
            'query or fragment components.'
        )
    ),
    error: str | None = fastapi.Query(
        default=None,
        title="Error code",
        description=(
            "The error code returned by the authorization server if "
            "the user cancelled the request, refused consent, or "
            "failed to authenticate."
        )
    )
) -> models.TokenResponse:
    if error:
        return None # type: ignore
    if code is None:
        raise FatalAuthorizationException(
            "The 'code' parameter is missing from the downstream "
            "response."
        )
    if state is None:
        raise FatalAuthorizationException(
            "The 'state' parameter is missing from the downstream "
            "response."
        )
    if state != expected_state:
        raise FatalAuthorizationException(
            "The 'state' parameter does not match the expected "
            "value."
        )
    if iss is not None and iss != client.issuer:
        raise FatalAuthorizationException(
            "The 'iss' parameter mismatches the expected issuer."
        )
    dto = models.AuthorizationCode(code=code, state=state)
    try:
        token = await client.token(
            dto,
            redirect_uri=(
                f'{request.url.scheme}://'
                f'{request.url.netloc}{request.url.path}'
            ),
        )
    except Error as e:
        logger.critical(
            "Unable to obtain token (error: %s, message: %s)",
            e.error, e.error_description
        )
        raise FrontendException(redirect_uri, error='temporarily_unavailable')

    await client.verify_response(
        response=token,
        nonce=expected_nonce,
        audience={client.client_id},
        issuer=iss
    )
    return token

FrontendTokenResponse: models.TokenResponse = fastapi.Depends(get)