import os
from base64 import b64encode, b64decode

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, hmac
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

from intranet.webauth.lib.settings import WEBAUTH_CRYPTOGRAPHY_KEY

CRYPTOGRAPHY_BACKEND = default_backend()


def encrypt(message):
    padder = padding.PKCS7(128).padder()
    padded_message = padder.update(message) + padder.finalize()

    iv = os.urandom(16)
    cipher = Cipher(algorithms.AES(WEBAUTH_CRYPTOGRAPHY_KEY), modes.CBC(iv), backend=CRYPTOGRAPHY_BACKEND)
    encryptor = cipher.encryptor()
    encrypted_message = encryptor.update(padded_message) + encryptor.finalize()

    return b64encode(iv) + ':' + b64encode(encrypted_message)


def get_hash(message):
    message = message.encode('utf-8')
    hasher = hashes.Hash(hashes.SHA256(), backend=default_backend())
    hasher.update(message)
    result = hasher.finalize()
    return b64encode(result)


def get_hmac(message):
    hasher = hmac.HMAC(WEBAUTH_CRYPTOGRAPHY_KEY, hashes.SHA256(), backend=default_backend())
    hasher.update(message)
    signature = hasher.finalize()
    return b64encode(signature)


def sign(message):
    return message + ':' + get_hmac(message)


def verify(signed_message):
    try:
        divider = signed_message.rfind(':')
        message, signature = signed_message[:divider], signed_message[divider+1:]
        if signature != b64encode(b64decode(signature)):
            return False, None
        signature = b64decode(signature)
    except Exception:
        return False, None
    try:
        hasher = hmac.HMAC(WEBAUTH_CRYPTOGRAPHY_KEY, hashes.SHA256(), backend=default_backend())
        hasher.update(message)
        hasher.verify(signature)
    except Exception:
        return False, message

    return True, message


def decrypt(original_message):
    divider = original_message.find(':')
    iv, encrypted_message = b64decode(original_message[:divider]), b64decode(original_message[divider+1:])
    cipher = Cipher(algorithms.AES(WEBAUTH_CRYPTOGRAPHY_KEY), modes.CBC(iv), backend=CRYPTOGRAPHY_BACKEND)
    decryptor = cipher.decryptor()
    decrypted_message = decryptor.update(encrypted_message) + decryptor.finalize()

    unpadder = padding.PKCS7(128).unpadder()
    message = unpadder.update(decrypted_message) + unpadder.finalize()

    return message


def decrypt_signed_value(data):
    data = data.encode('utf-8')
    status, message = verify(data)
    if not status:
        return None
    return decrypt(message).decode('utf-8')


def encrypt_and_sign(data):
    if type(data) == unicode:
        data = data.encode('utf-8')
    return sign(encrypt(data))
