import base64
import os

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

DEFAULT_SALT = 'HsPyLib'


def encode(in_file, out_file, encoding: str = 'utf-8') -> int:
    """Encode file into base64
    :param in_file: The file to be encoded
    :param out_file: The resulting encoded file
    :param encoding: The text encoding
    """
    with open(in_file, 'r') as f_in_file:
        with open(out_file, 'w') as f_out_file:
            data = base64.b64encode(str.encode(f_in_file.read()))
            return f_out_file.write(str(data, encoding=encoding))


def decode(in_file, out_file, encoding: str = 'utf-8') -> int:
    """Decode file from base64
    :param in_file: The file to be decoded
    :param out_file: The resulting decoded file
    :param encoding: The text encoding
    """
    with open(in_file, 'r') as f_in_file:
        with open(out_file, 'w') as f_out_file:
            data = base64.b64decode(f_in_file.read())
            return f_out_file.write(str(data, encoding=encoding))


def encrypt(
        in_file: str,
        out_file: str,
        pass_phrase: str,
        salt: str = DEFAULT_SALT,
        digest_algo=hashes.SHA256(),
        length=32,
        iterations=100000,
        encoding: str = 'utf-8') -> None:
    """Encrypt file using Fernet cryptography
    :param in_file: The file to be encrypted
    :param out_file: The resulting encrypted file
    :param pass_phrase: The passphrase to encrypt the file
    :param salt: TODO
    :param digest_algo: The digest encrypting algorithm
    :param length: TODO
    :param iterations: TODO
    :param encoding:
    """
    kdf = PBKDF2HMAC(
        algorithm=digest_algo,
        length=length,
        salt=salt.encode(encoding),
        iterations=iterations,
        backend=default_backend()
    )
    key = base64.urlsafe_b64encode(kdf.derive(pass_phrase.encode(encoding)))
    f = Fernet(key)
    assert os.path.exists(in_file), "Input file \"{}\" does not exist".format(in_file)
    with open(in_file) as f_in_file:
        with open(out_file, 'w') as f_out_file:
            f_out_file.write(f.encrypt(f_in_file.read().encode(encoding)).decode(encoding))
    assert os.path.exists(out_file), "Unable to encrypt file \"{}\"".format(in_file)


def decrypt(
        in_file: str,
        out_file: str,
        pass_phrase: str,
        salt: str = DEFAULT_SALT,
        digest_algo=hashes.SHA256(),
        length=32,
        iterations=100000,
        encoding: str = 'utf-8') -> None:
    """Decrypt file using Fernet cryptography
    :param in_file: The file to be decrypted
    :param out_file: The resulting decrypted file
    :param pass_phrase: The passphrase to decrypt the file
    :param salt: TODO
    :param digest_algo: The digest decrypting algorithm
    :param length: TODO
    :param iterations: TODO
    :param encoding:
    """
    kdf = PBKDF2HMAC(
        algorithm=digest_algo,
        length=length,
        salt=salt.encode(encoding),
        iterations=iterations,
        backend=default_backend()
    )
    key = base64.urlsafe_b64encode(kdf.derive(pass_phrase.encode(encoding)))
    f = Fernet(key)
    assert os.path.exists(in_file), "Input file \"{}\" does not exist".format(in_file)
    with open(in_file) as f_in_file:
        with open(out_file, 'w') as f_out_file:
            f_out_file.write(f.decrypt(f_in_file.read().encode(encoding)).decode(encoding))
    assert os.path.exists(out_file), "Unable to decrypt file \"{}\"".format(in_file)
