"""
python kmc adapter
"""
import os
import platform
from distutils.sysconfig import get_python_lib
from ctypes import CDLL
from ctypes import c_char_p
from ctypes import c_int
from ctypes import c_uint

PYTHON_PATH = get_python_lib(plat_specific=1)
LD_LIBRARY_PATH = [PYTHON_PATH + "/kmc/lib"]
os.getenv('LD_LIBRARY_PATH') and LD_LIBRARY_PATH.append(
    os.getenv('LD_LIBRARY_PATH'))
os.environ['LD_LIBRARY_PATH'] = ":".join(LD_LIBRARY_PATH)

system_type = platform.system()
if system_type == "Windows":
    _KMC_LIB_PATH = os.path.realpath(os.path.join(PYTHON_PATH, "kmc/lib/kmc_adapter.dll"))
else:
    _KMC_LIB_PATH = os.path.realpath(os.path.join(PYTHON_PATH, "kmc/lib/libkmc_adapter.so"))


class KMC_DOMAIN(object):  # noqa pylint: disable=invalid-name
    values = (DEFAULT, CONFIG_FILE, KEYSTONE_TOKEN) = (0, 1, 2)


PARAMS_SEPARATOR = '### ###'
CIPHER_TYPE_DEFAULT = 'generalCipher'

_KMC = None


def _get_cipher_type(cipher_type=None):
    valid_cipher_list = ['generalCipher', "SMCompatible"]
    return cipher_type if cipher_type in valid_cipher_list else CIPHER_TYPE_DEFAULT


class KMC(object):
    def __init__(self, cipher_type):
        global _KMC
        if _KMC is None:
            _KMC = self._load_kmc()
        self.kmc = _KMC
        self.cipher_type = cipher_type.encode("utf-8")

    def __enter__(self):
        init_ret = self.kmc.init(self.cipher_type)
        if init_ret != 0:
            raise Exception(f"Init kmc failed when {self.cipher_type.decode()}.")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        uninit_ret = self.kmc.uninit()
        if uninit_ret != 0:
            raise Exception(f"Uninit kmc failed when {self.cipher_type.decode()}.")

    @staticmethod
    def _load_kmc():
        kmc = CDLL(_KMC_LIB_PATH)
        kmc.encrypt.argtypes = [c_char_p, c_int, c_char_p]
        kmc.decrypt.argtypes = [c_int, c_char_p]
        kmc.createMK.argtypes = [c_int]
        kmc.activeMK.argtypes = [c_int, c_int]
        kmc.removeMK.argtypes = [c_int, c_int]
        kmc.fileEncrypt.argtypes = [c_char_p, c_int, c_char_p, c_char_p]
        kmc.fileDecrypt.argtypes = [c_int, c_char_p, c_char_p]
        kmc.init.restype = c_int
        kmc.uninit.restype = c_int
        kmc.updateRootkey.restype = c_int
        kmc.encrypt.restype = c_char_p
        kmc.decrypt.restype = c_char_p
        kmc.createMK.restype = c_int
        kmc.activeMK.restype = c_int
        kmc.removeMK.restype = c_int
        kmc.fileEncrypt.restype = c_int
        kmc.fileDecrypt.restype = c_int
        kmc.importMkFile.argtypes = [c_char_p, c_char_p, c_int]
        kmc.exportMkFile.argtypes = [c_char_p, c_char_p, c_int]
        kmc.hmac.argtypes = [c_char_p, c_uint, c_char_p]
        kmc.hmac.restype = c_char_p
        kmc.verifyHmac.argtypes = [c_uint, c_char_p, c_char_p]
        kmc.verifyHmac.restype = c_uint
        kmc.fileHmac.argtypes = [c_char_p, c_uint, c_char_p]
        kmc.fileHmac.restype = c_char_p
        kmc.verifyFileHmac.argtypes = [c_uint, c_char_p, c_char_p]
        kmc.verifyFileHmac.restype = c_uint
        kmc.protectPwd.argtypes = [c_char_p, c_char_p]
        kmc.protectPwd.restype = c_char_p
        kmc.verifyPwd.argtypes = [c_char_p, c_char_p]
        kmc.verifyPwd.restype = c_uint
        return kmc

    def update_root_key(self):
        return self.kmc.updateRootkey()

    def encrypt(self, domain_id, plain_text=None):
        return self.kmc.encrypt(self.cipher_type, int(domain_id), plain_text.encode("utf-8")).decode()

    def decrypt(self, domain_id, encrypt_data):
        return self.kmc.decrypt(int(domain_id), encrypt_data.encode("utf-8")).decode()

    def create_mk(self, domain_id):
        return self.kmc.createMK(int(domain_id))

    def active_mk(self, domain_id, kid):
        return self.kmc.activeMK(int(domain_id), int(kid))

    def remove_mk(self, domain_id, kid):
        return self.kmc.removeMK(int(domain_id), int(kid))

    def file_encrypt(self, domain_id, plain_file, _f):
        return self.kmc.fileEncrypt(self.cipher_type, int(domain_id), plain_file.encode("utf-8"), _f.encode("utf-8"))

    def file_decrypt(self, domain_id, _f, plain_file):
        return self.kmc.fileDecrypt(int(domain_id), _f.encode("utf-8"), plain_file.encode("utf-8"))

    def import_mk_file(self, mk_file, command):
        return self.kmc.importMkFile(mk_file.encode("utf-8"), command.encode("utf-8"), len(command))

    def export_mk_file(self, mk_file, command):
        return self.kmc.exportMkFile(mk_file.encode("utf-8"), command.encode("utf-8"), len(command))

    def hmac(self, domain_id, plain_text):
        return self.kmc.hmac(self.cipher_type, int(domain_id), plain_text.encode("utf-8")).decode()

    def verify_hmac(self, domain_id, plain_text, hmac_text):
        return self.kmc.verifyHmac(int(domain_id), plain_text.encode("utf-8"), hmac_text.encode("utf-8"))

    def file_hmac(self, domain_id, plain_file):
        return self.kmc.fileHmac(self.cipher_type, int(domain_id), plain_file.encode("utf-8")).decode()

    def verify_file_hmac(self, domain_id, plain_file, hmac_text):
        return self.kmc.verifyFileHmac(int(domain_id), plain_file.encode("utf-8"), hmac_text.encode("utf-8"))

    def protect_pwd(self, plain_text):
        return self.kmc.protectPwd(self.cipher_type, plain_text.encode("utf-8")).decode()

    def verify_pwd(self, plain_text, cipher_text):
        return self.kmc.verifyPwd(plain_text.encode("utf-8"), cipher_text.encode("utf-8"))


class API(object):
    def __init__(self):
        pass

    @staticmethod
    def update_root_key():
        cipher_type = _get_cipher_type(None)
        with KMC(cipher_type) as kmc:
            return kmc.update_root_key()

    @staticmethod
    def encrypt(domain_id, plain_text=None, cipher_type=None):
        if not plain_text:
            plain_text = os.getenv("PLAIN_TEXT")
        if not plain_text:
            raise RuntimeError("Parameter plain_text should not be null.")

        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            cipher = kmc.encrypt(domain_id, plain_text)
        if not cipher:
            raise RuntimeError("KMC soft encrypt failed.")

        return cipher

    @staticmethod
    def decrypt(domain_id, encrypt_data=None, cipher_type=None):
        if not encrypt_data:
            encrypt_data = os.getenv("ENCRYPT_DATA")
        if not encrypt_data:
            raise RuntimeError("Parameter encrypt_data should not be null.")

        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            plain = kmc.decrypt(domain_id, encrypt_data)
        if not plain:
            raise RuntimeError("KMC decrypt failed.")
        return plain

    @staticmethod
    def create_mk(domain_id, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.create_mk(domain_id)

    @staticmethod
    def active_mk(domain_id, kid, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.active_mk(domain_id, kid)

    @staticmethod
    def remove_mk(domain_id, kid, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.remove_mk(domain_id, kid)

    @staticmethod
    def file_encrypt(domain_id, plain_file, enc_file, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.file_encrypt(domain_id, plain_file, enc_file)

    @staticmethod
    def file_decrypt(domain_id, enc_file, plain_file, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.file_decrypt(domain_id, enc_file, plain_file)

    @staticmethod
    def import_mk_file(mk_file, command, cipher_type=None):
        """Import the mk file

        :param mk_file:
        :param command: the command user input,no special restrictions
        but the export command must be same as this.
        :return result is True or False
        """
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.import_mk_file(mk_file, command)

    @staticmethod
    def export_mk_file(mk_file, command, cipher_type=None):
        """Export the mk file

        :param mk_file: key file path
        :param command: the command user input,no special restrictions
        but the import command must be same as this.
        :return result is True or False
        """
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.export_mk_file(mk_file, command)

    @staticmethod
    def hmac(domain_id, plain_text, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.hmac(domain_id, plain_text)

    @staticmethod
    def verify_hmac(domain_id, plain_text, hmac_text, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.verify_hmac(domain_id, plain_text, hmac_text)

    @staticmethod
    def file_hmac(domain_id, plain_file, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            hmac_text = kmc.file_hmac(domain_id, plain_file)
        hmac_file = os.path.realpath(plain_file) + ".hmac"
        with open(hmac_file, 'w') as fp:
            fp.writelines(hmac_text)

    @staticmethod
    def verify_file_hmac(domain_id, plain_file, cipher_type=None):
        hmac_file = os.path.realpath(plain_file) + ".hmac"
        if not os.path.isfile(plain_file) or not os.path.isfile(hmac_file):
            return False
        with open(hmac_file) as fp:
            hmac_text = fp.readline()
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.verify_file_hmac(domain_id, hmac_file, hmac_text)

    @staticmethod
    def protect_pwd(plain_text, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.protect_pwd(plain_text)

    @staticmethod
    def verify_pwd(plain_text, cipher_text, cipher_type=None):
        cipher_type = _get_cipher_type(cipher_type)
        with KMC(cipher_type) as kmc:
            return kmc.verify_pwd(plain_text, cipher_text)
