import logging
import os.path
import re

from ibmsecurity.utilities.tools import files_same, get_random_temp_dir, version_compare
import shutil

logger = logging.getLogger(__name__)


def get_all(isamAppliance, kdb_id, check_mode=False, force=False):
    """
    Retrieving signer certificate names and details in a certificate database
    """
    return isamAppliance.invoke_get("Retrieving signer certificate names and details in a certificate database",
                                    f"/isam/ssl_certificates/{kdb_id}/signer_cert")


def get(isamAppliance, kdb_id, cert_id=None, label=None, check_mode=False, force=False):
    """
    Retrieving a signer certificate from a certificate database
    use cert_id (legacy) or label , mutually exclusive, label takes precedence
    """
    if label is None:
        retObj = isamAppliance.invoke_get("Retrieving a signer certificate from a certificate database",
                                    f"/isam/ssl_certificates/{kdb_id}/signer_cert/{cert_id}",
                                    ignore_error=True)
    else:
        cert_id = label
        retObj = isamAppliance.invoke_get("Retrieving a signer certificate from a certificate database",
                                    f"/isam/ssl_certificates/{kdb_id}/signer_cert/{label}",
                                    ignore_error=True)
    if retObj.get('rc', 0) == 404:
        return isamAppliance.create_return_object(rc=404, warnings=[f"{cert_id} does not exist in {kdb_id}"])
    else:
        return retObj

def load(isamAppliance, kdb_id, label, server, port, check_remote=False, check_mode=False, force=False):
    """
    Load a certificate from a server

    check_remote controls if ansible should check remote certificate by retrieving it or simply by
    checking for existence of the label in the kdb
    """
    if check_remote:
      logger.debug("Compare remote certificate with the one on the appliance. Use check_remote=False to switch to simple label checking.")
      tmp_check = _check_load(isamAppliance, kdb_id, label, server, port)
    else:
      logger.debug("Check for existence of the label in the kdb. Use check_remote=True to switch to advanced remote certificate with appliance certificate checking.")
      tmp_check = _check(isamAppliance, kdb_id, label)

    if force is True or tmp_check is False:
        if check_mode is True:
            return isamAppliance.create_return_object(changed=True)
        else:
            return isamAppliance.invoke_post(
                "Load a certificate from a server",
                f"/isam/ssl_certificates/{kdb_id}/signer_cert",
                {
                    "operation": 'load',
                    "label": label,
                    "server": server,
                    "port": port
                })

    return isamAppliance.create_return_object()


def _check_expired(notafter_epoch):
    """
    Can be used to check for expired certs
    Returns True if expired, False otherwise
    """
    import time
    epoch_time = int(time.time())
    cert_epoch = int(notafter_epoch)
    return cert_epoch < epoch_time


def _check_load(isamAppliance, kdb_id, label, server, port):
    """
    Checks if certificate to be loaded on the Appliance exists and if so, whether it is different from
    the one on the remote host.

    If the certificate exists on the Appliance, but has a different label,
    we return True, so that load() takes no action.

    If the requested label matches an existing label on the appliance,
    but the certs are different, check to see if cert is expired.  If so, replace cert.
    If not, do not replace.
    """
    import ssl
    remote_cert_pem = ssl.get_server_certificate((server, port))

    # Look for remote_cert_pem on in the signer certs on the appliance
    ret_obj = get_all(isamAppliance, kdb_id)
    for cert_data in ret_obj['data']:
        cert_id = cert_data['id']
        cert_pem = get(isamAppliance, kdb_id, cert_id)['data']['contents']
        if cert_id == label:  # label exists on appliance already
            if cert_pem == remote_cert_pem:
                logger.debug("The certificate already exits on the appliance with the same label name and same content.")
                return True  # both the labels and certificates match
            else:
                # Labels match, but the certs are different, so we need to update it.
                # However, you cannot load a cert with the same label name onto the appliance, since you get
                #   CTGSK2021W A duplicate certificate already exists in the database.
                # We delete the cert from the appliance and return False to the load() function,
                # so that we can load the new one
                ret_obj = delete(isamAppliance, kdb_id, cert_id)
                logger.debug("Labels match, but the certs are different, so we need to update it.")
                return False
        else:
            if cert_pem == remote_cert_pem:  # cert on the appliance, but with a different name
                logger.info(
                    f"The certifcate is already on the appliance, but it has a different label name. The existing label name is {label} and requested label name is {cert_id}")
                return True
    return False


def delete(isamAppliance, kdb_id, cert_id=None, label=None, check_mode=False, force=False):
    """
    Deleting a signer certificate from a certificate database
    """
    if label is not None:
        cert_id = label

    if force or _check(isamAppliance, kdb_id, cert_id) is True:
        if check_mode is True:
            return isamAppliance.create_return_object(changed=True)
        else:
            try:
                # Assume Python3 and import package
                from urllib.parse import quote
            except ImportError:
                # Now try to import Python2 package
                from urllib import quote

            # URL being encoded primarily to handle spaces and other special characers in them
            f_uri = f"/isam/ssl_certificates/{kdb_id}/signer_cert/{cert_id}"
            full_uri = quote(f_uri)
            return isamAppliance.invoke_delete(
                "Deleting a signer certificate from a certificate database", full_uri)

    return isamAppliance.create_return_object()


def export_cert(isamAppliance, kdb_id, cert_id, filename, check_mode=False, force=False):
    """
    Exporting a signer certificate from a certificate database
    """
    import os.path

    if force or _check(isamAppliance, kdb_id, cert_id):
        if check_mode is False:  # No point downloading a file if in check_mode
            return isamAppliance.invoke_get_file(
                "Export a certificate database",
                f"/isam/ssl_certificates/{kdb_id}/signer_cert/{cert_id}?export",
                filename)

    return isamAppliance.create_return_object()


def import_cert(isamAppliance, kdb_id, cert, label, preserve_label='false', check_mode=False, force=False):
    """
    Importing a signer certificate into a certificate database
    cert can be a file or a string
    """
    # Let's do some simple check
    # check if the string begins with -----BEGIN CERTIFICATE-----
    #
    if cert.startswith('-----BEGIN CERTIFICATE-----'):
      if force or _check_import_string(isamAppliance, kdb_id, label, cert, check_mode=check_mode):
          if check_mode:
              return isamAppliance.create_return_object(changed=True)
          else:
              json_data = {}
              json_data['label'] = label
              json_data['cert'] = cert
              json_data['operation'] = "import" # this is missing from the documentation.
              if version_compare(isamAppliance.facts['version'], "10.0.5.0") >= 0:
                  json_data['preserve_label'] = preserve_label.lower()
              ret_obj = isamAppliance.invoke_post("Create signer cert from certificate string",
                                               f"/isam/ssl_certificates/{kdb_id}/signer_cert",
                                               json_data,
                                               ignore_error=True)
              if preserve_label.lower() == 'true':
                  if ret_obj['rc'] == 500:
                      # this most likely means the certificate exists already (with a different name ?)
                      if "DPWAP0134E" in ret_obj['data'].get('message', ''):
                          return isamAppliance.create_return_object(rc=0, warnings=(f"Not updating existing certificate (preserve_label=true): {ret_obj.get('data', {}).get('message', '')}"), changed=False)
                      # this most likely means the certificate exists already (with a different name ?)
                      if "duplicate" in ret_obj['data'].get('message', ''):
                          return isamAppliance.create_return_object(rc=0, warnings=(f"Not updating existing certificate (preserve_label=true): {ret_obj.get('data', {}).get('message', '')}"), changed=False)
              else:
                  if ret_obj['rc'] == 500:
                      # this most likely means the certificate exists already (with a different name ?)
                      if "DPWAP0134E" in ret_obj['data'].get('message', ''):
                          return isamAppliance.create_return_object(rc=0, warnings=(f"Not updating existing certificate (preserve_label=false): {ret_obj.get('data', {}).get('message', '')}"), changed=False)
              if ret_obj['rc'] == 404:
                  logger.info(f"Not found... which is odd")
              return ret_obj

    else:
      if force or _check_import(isamAppliance, kdb_id, label, cert, check_mode=check_mode):
          if check_mode:
              return isamAppliance.create_return_object(changed=True)
          else:
              if version_compare(isamAppliance.facts['version'], "10.0.5.0") < 0:
                  return isamAppliance.invoke_post_files(
                      "Importing a signer certificate into a certificate database",
                      f"/isam/ssl_certificates/{kdb_id}/signer_cert",
                      [
                          {
                              'file_formfield': 'cert',
                              'filename': cert,
                              'mimetype': 'application/octet-stream'
                          }
                      ],
                      {'label': label})
              else:
                  # Preserve label true , throws 500 error
                  ret_obj = isamAppliance.invoke_post_files(
                      "Importing a signer certificate into a certificate database",
                      f"/isam/ssl_certificates/{kdb_id}/signer_cert",
                      [
                          {
                              'file_formfield': 'cert',
                              'filename': cert,
                              'mimetype': 'application/octet-stream'
                          }
                      ],
                      {'label': label,
                       'preserve_label': preserve_label.lower()},
                      ignore_error=True
                  )
                  if preserve_label.lower() == 'true':
                      if ret_obj['rc'] == 500:
                          # this most likely means the certificate exists already (with a different name ?)
                          if "DPWAP0134E" in ret_obj['data'].get('message', ''):
                              return isamAppliance.create_return_object(rc=0, warnings=(
                                  f"Not updating existing certificate (preserve_label=true): {ret_obj.get('data', {}).get('message', '')}"),
                                                                        changed=False)
                          # this most likely means the certificate exists already (with a different name ?)
                          if "duplicate" in ret_obj['data'].get('message', ''):
                              return isamAppliance.create_return_object(rc=0, warnings=(
                                  f"Not updating existing certificate (preserve_label=true): {ret_obj.get('data', {}).get('message', '')}"),
                                                                        changed=False)
                  else:
                      if ret_obj['rc'] == 500:
                          # this most likely means the certificate exists already (with a different name ?)
                          if "DPWAP0134E" in ret_obj['data'].get('message', ''):
                              return isamAppliance.create_return_object(rc=0, warnings=(
                                  f"Not updating existing certificate (preserve_label=false): {ret_obj.get('data', {}).get('message', '')}"),
                                                                        changed=False)
                  if ret_obj['rc'] == 404:
                      logger.info(f"Not found... which is odd")
                  return ret_obj

    return isamAppliance.create_return_object()


def _check(isamAppliance, kdb_id, cert_id):
    """
    Check if signer certificate already exists in certificate database
    """
    ret_obj = get_all(isamAppliance, kdb_id)

    for certdb in ret_obj['data']:
        if certdb.get('id', None) == cert_id:
            return True

    return False


def _check_import_string(isamAppliance, kdb_id, label, certstring, check_mode=False):
    cert_pem = get(isamAppliance, kdb_id, label)
    cert_pem = cert_pem.get("data", {})
    cert_pem = cert_pem.get("contents", "")
    if cert_pem == "":
        # No certificate found.  Fine.
        return True
    if cert_pem.replace(" ", "").replace("\n", "").replace("\r", "") == certstring.replace(" ", "").replace("\n", "").replace("\r", ""):
        logger.debug(f"Certificate already exists with same label {label}")
        return False
    return True


def _check_import(isamAppliance, kdb_id, cert_id, filename, check_mode=False):
    """
    Checks if certificate on the Appliance  exists and if so, whether it is different from
    the one stored in filename
    This is not a full check; because the certificate could exist under a different label !
    filename may be a chain, in that case we need to compare a bit more
    """
    with open(filename) as file:
        newcert = file.read() # tuple
        # split into separate certs , if it's a chain
        newcert = re.findall(r'(-----[BEGIN \S\ ]+?-----[\S\s]+?-----[END \S\ ]+?-----)', newcert)

    cert_pem = get(isamAppliance, kdb_id, cert_id)
    cert_pem = cert_pem.get("data", {})
    cert_pem = cert_pem.get("contents", "")
    if cert_pem == "":
        # No certificate found.  Fine.
        return True

    for nc in newcert:
        if cert_pem.replace(" ", "").replace("\n", "").replace("\r", "") == nc.replace(" ", "").replace("\n", "").replace("\r", ""):
            logger.debug(f"Signer Certificate already exists with same label {cert_id}")
            return False
    return True


def compare(isamAppliance1, isamAppliance2, kdb_id):
    """
    Compare signer certificates in certificate database between two appliances
    """
    ret_obj1 = get_all(isamAppliance1, kdb_id)
    ret_obj2 = get_all(isamAppliance2, kdb_id)

    import ibmsecurity.utilities.tools
    return ibmsecurity.utilities.tools.json_compare(ret_obj1, ret_obj2, deleted_keys=[])
