"""File operations for User authorization."""

from sqlalchemy import exists
from sqlalchemy.orm.exc import NoResultFound
from hydrus.data.exceptions import UserExists, UserNotFound
from hydrus.data.db_models import User, Token, Nonce
from hashlib import sha224
import base64
# import random
from sqlalchemy.orm.session import Session
from werkzeug.local import LocalProxy
from random import randrange
from datetime import datetime, timedelta
from uuid import uuid4


def add_user(id_: int, paraphrase: str, session: Session) -> None:
    """Add new users to the database.

    Raises:
        UserExits: If a user with `id_` already exists.

    """
    if session.query(exists().where(User.id == id_)).scalar():
        raise UserExists(id_=id_)
    else:
        new_user = User(id=id_, paraphrase=sha224(
            paraphrase.encode('utf-8')).hexdigest())
        session.add(new_user)
        session.commit()


def check_nonce(request: LocalProxy, session: Session) -> bool:
    """check validity of nonce passed by the user."""
    try:
        id_ = request.headers['X-Authentication']
        nonce = session.query(Nonce).filter(Nonce.id == id_).one()
        present = datetime.now()
        present = present - nonce.timestamp
        session.delete(nonce)
        session.commit()
        if present > timedelta(0, 0, 0, 0, 1, 0, 0):
            return False
    except BaseException:
        return False
    return True


def create_nonce(session: Session) -> str:
    """
    Create a one time use nonce valid for a short time
    for user authentication.
    """
    nonce = str(uuid4())
    time = datetime.now()
    new_nonce = Nonce(id=nonce, timestamp=time)
    session.add(new_nonce)
    session.commit()
    return nonce


def add_token(request: LocalProxy, session: Session) -> str:
    """
    Create a new token for the user or return a
    valid existing token to the user.
    """
    token = None
    id_ = int(request.authorization['username'])
    try:
        token = session.query(Token).filter(Token.user_id == id_).one()
        if not token.is_valid():
            update_token = '%030x' % randrange(16**30)
            token.id = update_token
            token.timestamp = datetime.now()
            session.commit()
    except NoResultFound:
        token = '%030x' % randrange(16**30)
        new_token = Token(user_id=id_, id=token)
        session.add(new_token)
        session.commit()
        return token
    return token.id


def check_token(request: LocalProxy, session: Session) -> bool:
    """
    check validity of the token passed by the user.
    """
    token = None
    try:
        id_ = request.headers['X-Authorization']
        token = session.query(Token).filter(Token.id == id_).one()
        if not token.is_valid():
            token.delete()
            return False
    except BaseException:
        return False
    return True


def generate_basic_digest(id_: int, paraphrase: str) -> str:
    """Create the digest to be added to the HTTP Authorization header."""
    paraphrase_digest = sha224(paraphrase.encode('utf-8')).hexdigest()
    credentials = '{}:{}'.format(id_, paraphrase_digest)
    digest = base64.b64encode(credentials.encode('utf-8')).decode('utf-8')
    return digest


def authenticate_user(id_: int, paraphrase: str, session: Session) -> bool:
    """Authenticate a user based on the ID and his paraphrase.

    Raises:
        UserNotFound: If a user with `id_` is not a valid/defined User

    """
    user = None
    try:
        user = session.query(User).filter(User.id == id_).one()
    except NoResultFound:
        raise UserNotFound(id_=id_)
    hashvalue = user.paraphrase
    generated_hash = sha224(paraphrase.encode('utf-8')).hexdigest()

    return generated_hash == hashvalue


def check_authorization(request: LocalProxy, session: Session) -> bool:
    """Check if the request object has the correct authorization."""
    auth = request.authorization
    if check_nonce(request, session):
        return authenticate_user(auth.username, auth.password, session)
    return False