# ------------------------------------------------------------------------------
# Copyright (c) 2022 Korawich Anuttra. All rights reserved.
# Licensed under the MIT License. See LICENSE in the project root for
# license information.
# ------------------------------------------------------------------------------
from datetime import datetime, timedelta, timezone
from typing import Any, Optional, Union

import bcrypt
import jwt
from fastapi import HTTPException, Request
from fastapi import status as st
from fastapi.security import OAuth2PasswordBearer
from fastapi.security.utils import get_authorization_scheme_param

from ..conf import config

ALGORITHM: str = "HS256"


class OAuth2PasswordBearerCookie(OAuth2PasswordBearer):

    # IMPORTANT: it will raise Request does not exists when use
    #   `from __future__ import annotations` on above script file.
    async def __call__(self, request: Request) -> Optional[str]:

        # NOTE: if the header has authorization key, it will use this value with
        #   the first priority.
        if request.headers.get("Authorization"):
            return await super().__call__(request)

        # NOTE: get authorization key from the cookie.
        authorization: Optional[str] = request.cookies.get("access_token")
        scheme, param = get_authorization_scheme_param(authorization)
        if not authorization or scheme.lower() != "bearer":
            if self.auto_error:
                raise HTTPException(
                    status_code=st.HTTP_401_UNAUTHORIZED,
                    detail="Not authenticated",
                    headers={"WWW-Authenticate": "Bearer"},
                )
            else:
                return None
        return param


oauth2_scheme = OAuth2PasswordBearerCookie(
    tokenUrl="api/v1/auth/token",
    scopes={
        "me": "Read information about the current user.",
        "workflows": "Read workflows and release logging.",
    },
    auto_error=False,
)


def create_access_token(
    subject: Union[str, dict[str, Any]],
    expires_delta: Union[timedelta, None] = None,
) -> str:
    if expires_delta:
        expire: datetime = datetime.now(timezone.utc) + expires_delta
    else:
        expire: datetime = datetime.now(timezone.utc) + timedelta(
            minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES
        )

    if isinstance(subject, dict):
        to_encode = subject.copy()
        to_encode.update({"exp": expire})
    else:
        to_encode = {"exp": expire, "sub": str(subject)}

    return jwt.encode(to_encode, config.OBSERVE_SECRET_KEY, algorithm=ALGORITHM)


def create_refresh_token(
    subject: Union[str, dict[str, Any]],
    expires_delta: Union[timedelta, None] = None,
) -> str:
    if expires_delta:
        expire: datetime = datetime.now(timezone.utc) + expires_delta
    else:
        expire: datetime = datetime.now(timezone.utc) + timedelta(
            minutes=config.REFRESH_TOKEN_EXPIRE_MINUTES
        )

    if isinstance(subject, dict):
        to_encode = subject.copy()
        to_encode.update({"exp": expire})
    else:
        to_encode = {"exp": expire, "sub": str(subject)}

    return jwt.encode(
        to_encode, config.OBSERVE_REFRESH_SECRET_KEY, algorithm=ALGORITHM
    )


def verify_password(plain_password: str, hashed_password: str) -> bool:
    return bcrypt.checkpw(plain_password.encode(), hashed_password.encode())


def get_password_hash(password: str) -> str:
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
