from secrets import compare_digest

from fastapi import Response, Request, HTTPException, status
from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.security import APIKeyCookie
from fastapi.security.api_key import APIKeyBase
from scripts.constants.secrets import Secrets
from scripts.db.redis_connections import login_db

from scripts.config import Service
from scripts.utils.security.apply_encrytion_util import create_token
from scripts.utils.security.jwt_util import JWT


class CookieAuthentication(APIKeyBase):
    """
    Authentication backend using a cookie.
    Internally, uses a JWT token to store the data.
    """

    scheme: APIKeyCookie
    cookie_name: str
    cookie_secure: bool

    def __init__(
            self,
            cookie_name: str = "login-token",

    ):
        super().__init__()
        self.model: APIKey = APIKey(**{"in": APIKeyIn.cookie}, name=cookie_name)
        self.scheme_name = self.__class__.__name__
        self.cookie_name = cookie_name
        self.scheme = APIKeyCookie(name=self.cookie_name, auto_error=False)
        self.login_redis = login_db
        self.jwt = JWT()

    def __call__(self, request: Request, response: Response) -> str:
        cookies = request.cookies
        login_token = cookies.get("login-token")
        if not login_token:
            login_token = request.headers.get("login-token")
        if not login_token:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)

        jwt_token = self.login_redis.get(login_token)
        if not jwt_token:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)

        try:
            decoded_token = self.jwt.validate(token=jwt_token)
            if not decoded_token:
                raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
        except Exception as e:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=e.args)

        user_id = decoded_token.get("user_id")

        cookie_user_id = request.cookies.get(
            "user_id", request.cookies.get(
                "userId", request.headers.get("userId")
            )
        )

        _token = decoded_token.get("token")
        _age = int(decoded_token.get("age", Secrets.LOCK_OUT_TIME_MINS))

        if not compare_digest(Secrets.token, _token):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
        if login_token != decoded_token.get("uid"):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
        if cookie_user_id and not compare_digest(user_id, cookie_user_id):
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)

        try:
            new_token = create_token(
                user_id=user_id,
                ip=request.client.host,
                token=Secrets.token,
                age=_age,
                login_token=login_token,
            )
        except Exception as e:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=e.args)
        response.set_cookie(
            'login-token',
            new_token,
            samesite='strict',
            httponly=True,
            secure=Service.secure_cookie,
            max_age=Secrets.LOCK_OUT_TIME_MINS * 60,
        )
        response.headers['login-token'] = new_token
        return user_id
