from uuid import UUID

import psycopg
import requests
from fastapi import Depends, HTTPException
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from jose import JWTError, jwk, jwt
from jose.backends.base import Key
from jose.exceptions import JWKError
from src.__types.errors import ResourceNotFoundError
from src.__types.index import ContractProvider
from src.infrastructure.repositories.contract_provider_repository import ContractProviderRepository
from src.logger import logger
from src.settings import Settings
from src.tracer import tracer

settings = Settings()

BEARER_TOKEN_DESCRIPTION = """
BearerトークンとしてアクセストークンをAuthorizationヘッダーに付与してください。
発行方法は[こちら](#アクセストークンの発行)を参照してください。
"""  # noqa: S105

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


@tracer.capture_method
def get_contract_provider(
    credential: HTTPAuthorizationCredentials = _bearer_dependency,
) -> ContractProvider:
    def get_kid(access_token: str) -> str:
        kid = jwt.get_unverified_header(access_token).get("kid")
        if kid is None:
            logger.debug("Invalid access token: 'kid' not found in header.")
            raise HTTPException(status_code=401, detail="Invalid access token.")
        return kid

    def get_rsa_key(kid: str) -> Key:
        if not settings.user_pool_jwks_endpoint_url:
            raise HTTPException(
                status_code=500,
                detail="User pool JWKS endpoint URL is not configured.",
            )
        jwks = requests.get(settings.user_pool_jwks_endpoint_url, timeout=3).json()
        key = {}
        for jwk_data in jwks["keys"]:
            if jwk_data["kid"] == kid:
                key = jwk_data
                break
        return jwk.construct(key)

    access_token = credential.credentials
    try:
        decoded_access_token = jwt.decode(
            token=access_token,
            key=get_rsa_key(get_kid(access_token)),
            algorithms=["RS256"],
            options={
                "verify_signature": True,
                "verify_aud": False,  # TODO: aud検証を有効にする
                "verify_iat": True,
                "verify_exp": True,
                "verify_nbf": True,
                "verify_iss": False,  # TODO: iss検証を有効にする
                "verify_sub": True,
                "verify_jti": True,
                "verify_at_hash": True,
                "require_aud": False,
                "require_iat": False,
                "require_exp": False,
                "require_nbf": False,
                "require_iss": False,
                "require_sub": False,
                "require_jti": False,
                "require_at_hash": False,
                "leeway": 0,
            },
        )
        contract_provider_id = decoded_access_token.get("contract_provider_id")
        if contract_provider_id is None:
            raise HTTPException(status_code=401, detail="Invalid access token.")
        try:
            contract_provider_id = UUID(contract_provider_id)
        except (ValueError, AttributeError) as e:
            raise HTTPException(status_code=401, detail="Invalid access token.") from e
        tracer.put_annotation("ContractProviderId", str(contract_provider_id))

        with psycopg.connect(settings.postgres_url) as connection:
            repository = ContractProviderRepository(connection=connection)
            return repository.get(
                contract_provider_id,
            )
    except JWTError as jwt_err:
        logger.debug("JWTError: Invalid access token.", exc_info=jwt_err)
        raise HTTPException(status_code=401, detail="Invalid access token.") from jwt_err
    except JWKError as jwk_err:
        logger.debug("JWKError: Invalid access token.", exc_info=jwk_err)
        raise HTTPException(status_code=401, detail="Invalid access token.") from jwk_err
    except ResourceNotFoundError as resource_not_found_err:
        logger.debug("ResourceNotFoundError: Contract Provider not found.", exc_info=resource_not_found_err)
        raise HTTPException(status_code=401, detail="Invalid access token.") from resource_not_found_err
