"""JWT Backend.

Navigator Authentication using JSON Web Tokens.
"""
import jwt
from aiohttp import web
from .base import BaseAuthBackend
from datetime import datetime, timedelta
from aiohttp_session import get_session, new_session
from navigator.exceptions import NavException, UserDoesntExists, InvalidAuth
from navigator.conf import (
    SESSION_TIMEOUT,
    SECRET_KEY
)
import hashlib
import base64
import secrets

# credentials algorithm
PWD_ALGORITHM = "pbkdf2_sha256"
ITERATIONS = 150000
PWD_DIGEST = 'sha256'
KEY_LENGTH = 64

# "%s$%d$%s$%s" % (algorithm, iterations, salt, hash)

class BasicAuth(BaseAuthBackend):
    """Basic User/pasword with JWT Authentication."""
    user_attribute: str = 'user'
    username_attribute: str = 'username'
    pwd_atrribute: str = 'password'
    scheme: str = 'Basic'

    async def validate_user(self, login: str = None, password: str = None):
        # get the user based on Model
        search = {
            self.username_attribute: login
        }
        try:
            user = await self.get_user(**search)
        except UserDoesntExists as err:
            raise UserDoesntExists(f'User {login} doesnt exists')
        except Exception as err:
            raise Exception(err)
        try:
            # later, check the password
            pwd = user[self.pwd_atrribute]
            if self.check_password(pwd, password):
                # return the user Object
                return user
            else:
                raise InvalidAuth('Invalid Credentials')
        except Exception as err:
            raise Exception(err)
        return None

    def set_password(
            self,
            password,
            token_num: int = 6,
            iterations: int = 80000,
            salt: str = None
    ):
        if not salt:
            salt = secrets.token_hex(token_num)
        key = hashlib.pbkdf2_hmac(
            PWD_DIGEST,
            password.encode('utf-8'),
            salt.encode('utf-8'),
            iterations,
            dklen=KEY_LENGTH
        )
        hash = base64.b64encode(key).decode('ascii').strip()
        return f'{PWD_ALGORITHM}${iterations}${salt}${hash}'

    def check_password(self, current_password, password):
        algorithm, iterations, salt, hash = current_password.split('$', 3)
        assert algorithm == PWD_ALGORITHM
        iterations = int(iterations)
        compare_hash = self.set_password(
            password,
            iterations=iterations,
            salt=salt
        )
        return secrets.compare_digest(current_password, compare_hash)


    async def get_payload(self, request):
        ctype = request.content_type
        if request.method == 'GET':
            try:
                user = request.query.get(self.user_attribute, None)
                password = request.query.get(self.pwd_atrribute, None)
                return [user, password]
            except Exception:
                return None
        elif ctype in ('multipart/mixed', 'application/x-www-form-urlencoded'):
            data = await request.post()
            if len(data) > 0:
                user = data.get(self.user_attribute, None)
                password = data.get(self.pwd_atrribute, None)
                return [user, password]
            else:
                return None
        elif ctype == 'application/json':
            try:
                data = await request.json()
                user = data[self.user_attribute]
                password = data[self.pwd_atrribute]
                return [user, password]
            except Exception:
                return None
        else:
            return None

    async def check_credentials(self, request):
        try:
            user, pwd = await self.get_payload(request)
        except Exception as err:
            raise NavException(err, state=400)
        if not pwd and not user:
            raise InvalidAuth('Invalid Credentials', state=401)
        else:
            # making validation
            try:
                user = await self.validate_user(login=user, password=pwd)
            except UserDoesntExists as err:
                raise UserDoesntExists(err)
            except InvalidAuth as err:
                raise InvalidAuth(err, state=401)
            except Exception as err:
                raise NavException(err, state=500)
            try:
                userdata = self.get_userdata(user)
                # Create the User session and returned.
                session = await self._session.create_session(
                    request,
                    user,
                    userdata
                )
                payload = {
                    self.user_property: user[self.userid_attribute],
                    self.username_attribute: user[self.username_attribute],
                    'user_id': user[self.userid_attribute]
                }
                token = self.create_jwt(data=payload)
                return {'token': token}
            except Exception as err:
                print(err)
                return False

    async def authenticate(self, request):
        """ Authenticate, refresh or return the user credentials."""
        pass

    async def auth_middleware(self, app, handler):
        async def middleware(request):
            request.user = None
            authz = await self.authorization_backends(app, handler, request)
            if authz:
                return await authz
            try:
                jwt_token = self.decode_token(request)
            except NavException as err:
                print('Error HERE: ', err, err.state)
                response = {
                    "message": "Token Error",
                    "error": err.message,
                    "status": err.state
                }
                print(response)
                return web.json_response(response, status=err.state)
            except Exception as err:
                raise web.HTTPBadRequest(
                    body=f'Bad Request: {err!s}'
                )
            if self.credentials_required is True:
                raise web.HTTPUnauthorized(
                    body='Unauthorized'
                )
            return await handler(request)
        return middleware
