"""
Helper module around the system's libssl library if available for IGE mode.
"""
import ctypes
import ctypes.util
import platform
import sys
try:
    import ctypes.macholib.dyld
except ImportError:
    pass
import logging
import os

__log__ = logging.getLogger(__name__)


def _find_ssl_lib():
    # macOS 10.15 segfaults on unversioned crypto libraries.
    # We therefore pin the current stable version here
    if sys.platform == 'darwin':
        release, _version_info, _machine = platform.mac_ver()
        ver, major, *_ = release.split('.')
        lib = None
        if int(ver) > 10 or int(ver) == 10 and int(major) > 14:
            lib = (
                ctypes.util.find_library('libssl.46') or
                ctypes.util.find_library('libssl.44') or
                ctypes.util.find_library('libssl.42')
            )
        if not lib:
            lib = ctypes.util.find_library('ssl')
        if lib:
            try:
                return ctypes.cdll.LoadLibrary(lib)
            except OSError:
                pass
        raise OSError('no library called "ssl" found on macOS')

    # Linux / other Unix systems
    lib = ctypes.util.find_library('ssl') or ctypes.util.find_library('crypto')
    if not lib:
        for fallback in ('libssl.so.3', 'libcrypto.so.3', 'libssl.so.1.1', 'libcrypto.so.1.1', 'libssl.so', 'libcrypto.so'):
            try:
                return ctypes.cdll.LoadLibrary(fallback)
            except OSError:
                continue
        raise OSError('no library called "ssl" or "crypto" found')

    try:
        return ctypes.cdll.LoadLibrary(lib)
    except OSError:
        for fallback in ('libssl.so.3', 'libcrypto.so.3', 'libssl.so.1.1', 'libcrypto.so.1.1', 'libssl.so', 'libcrypto.so'):
            try:
                return ctypes.cdll.LoadLibrary(fallback)
            except OSError:
                continue
        raise


try:
    import cryptg
    decrypt_ige = cryptg.decrypt_ige
    encrypt_ige = cryptg.encrypt_ige
    _cryptg_available = True
except ImportError:
    _cryptg_available = False

if not _cryptg_available:
    try:
        _libssl = _find_ssl_lib()
    except OSError as e:
        # See https://github.com/LonamiWebs/Telethon/issues/1167
        # Sometimes `find_library` returns improper filenames.
        __log__.info('Failed to load SSL library: %s (%s)', type(e), e)
        _libssl = None

    if not _libssl:
        decrypt_ige = None
        encrypt_ige = None
    else:
        # https://github.com/openssl/openssl/blob/master/include/openssl/aes.h
        AES_ENCRYPT = ctypes.c_int(1)
        AES_DECRYPT = ctypes.c_int(0)
        AES_MAXNR = 14

        class AES_KEY(ctypes.Structure):
            """Helper class representing an AES key"""
            _fields_ = [
                ('rd_key', ctypes.c_uint32 * (4 * (AES_MAXNR + 1))),
                ('rounds', ctypes.c_uint),
            ]

        def decrypt_ige(cipher_text, key, iv):
            aes_key = AES_KEY()
            key_len = ctypes.c_int(8 * len(key))
            c_key = ctypes.create_string_buffer(key, len(key))
            c_iv = ctypes.create_string_buffer(iv, len(iv))

            in_len = ctypes.c_size_t(len(cipher_text))
            in_ptr = ctypes.create_string_buffer(cipher_text, len(cipher_text))
            out_ptr = ctypes.create_string_buffer(len(cipher_text))

            _libssl.AES_set_decrypt_key(c_key, key_len, ctypes.byref(aes_key))
            _libssl.AES_ige_encrypt(
                in_ptr,
                out_ptr,
                in_len,
                ctypes.byref(aes_key),
                c_iv,
                AES_DECRYPT
            )

            return out_ptr.raw

        def encrypt_ige(plain_text, key, iv):
            aes_key = AES_KEY()
            key_len = ctypes.c_int(8 * len(key))
            c_key = ctypes.create_string_buffer(key, len(key))
            c_iv = ctypes.create_string_buffer(iv, len(iv))

            in_len = ctypes.c_size_t(len(plain_text))
            in_ptr = ctypes.create_string_buffer(plain_text, len(plain_text))
            out_ptr = ctypes.create_string_buffer(len(plain_text))

            _libssl.AES_set_encrypt_key(c_key, key_len, ctypes.byref(aes_key))
            _libssl.AES_ige_encrypt(
                in_ptr,
                out_ptr,
                in_len,
                ctypes.byref(aes_key),
                c_iv,
                AES_ENCRYPT
            )

            return out_ptr.raw
