from typing import Annotated
from uuid import UUID

from fastapi import Depends, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import jwt
from jose.exceptions import JWKError, JWTError
from src.__types.errors import ClientError, ResourceNotFoundError
from src.__types.index import Tenant
from src.logger import logger
from src.routers.management.authorizer.m2m_authorizer import get_m2m_tenant
from src.routers.management.authorizer.user_authorizer import get_user_tenant
from src.routers.management.headers import get_tenant_name
from src.tracer import tracer

BEARER_TOKEN_DESCRIPTION = """
Authorizationヘッダーに Bearer JWT を指定してください。

このAPIは以下のいずれかを受け付けます。
- クライアント認証: アクセストークン
- エンドユーザー認証: IDトークン

形式: Authorization: Bearer <JWT>
トークンの取得方法・利用方法は[認証](#bearerトークンの利用)を参照してください。
"""  # noqa: S105

_bearer_dependency = Depends(
    HTTPBearer(
        scheme_name="Bearer",
        auto_error=True,
        description=BEARER_TOKEN_DESCRIPTION,
    ),
)


@tracer.capture_method
def get_tenant(
    credential: HTTPAuthorizationCredentials = _bearer_dependency,
    header_tenant_name: Annotated[str | None, Depends(get_tenant_name)] = None,
) -> Tenant:
    """token_useを見てm2m/user専用authorizerに振り分ける。"""

    def _resolve() -> Tenant:
        token_use = jwt.get_unverified_claims(credential.credentials).get("token_use")

        if token_use == "access":  # noqa: S105 誤検知
            return get_m2m_tenant(credential=credential)
        if token_use == "id":  # noqa: S105 誤検知
            return get_user_tenant(
                credential=credential,
                header_tenant_name=header_tenant_name,
            )

        raise JWTError(f"Invalid token_use: expected 'access' or 'id', got '{token_use}'.")

    try:
        return _resolve()

    except JWTError as jwt_err:
        logger.debug("JWTError: Invalid token.", exc_info=jwt_err)
        raise HTTPException(status_code=401, detail="Invalid token.") from jwt_err

    except JWKError as jwk_err:
        logger.debug("JWKError: Invalid token.", exc_info=jwk_err)
        raise HTTPException(status_code=401, detail="Invalid token.") from jwk_err

    except ResourceNotFoundError as resource_not_found_err:
        logger.debug("ResourceNotFoundError: Tenant not found.", exc_info=resource_not_found_err)
        raise HTTPException(status_code=401, detail="Invalid token.") from resource_not_found_err

    except ClientError as client_err:
        # 外向けには401に統一する
        logger.debug("ClientError: Invalid request.", exc_info=client_err)
        raise HTTPException(status_code=401, detail="Invalid token.") from client_err

    except ValueError as value_err:
        logger.debug("ValueError: Configuration error.", exc_info=value_err)
        raise HTTPException(status_code=500, detail="Internal server error.") from value_err


def get_user_id(
    credential: HTTPAuthorizationCredentials = _bearer_dependency,
    _: Annotated[Tenant | None, Depends(get_tenant)] = None,  # トークン検証済みであることを保証するための依存関係
) -> UUID:
    """検証済みトークンから user_id を取得する。m2mトークンの場合は403。

    Raises:
        HTTPException: m2mトークンなど、user_idがトークンに含まれていない場合

    """
    claims = jwt.get_unverified_claims(credential.credentials)
    user_id = claims.get("custom:user_id")
    if user_id is None:
        raise HTTPException(status_code=403, detail="Forbidden.")
    return UUID(user_id)
