""" X509Request is a class for managing X509 requests with their Pkeys.
It's main use is for proxy delegation.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

__RCSID__ = "$Id$"

import M2Crypto
from DIRAC import S_OK, S_ERROR
from DIRAC.Core.Utilities import DErrno
# from DIRAC.Core.Security.m2crypto.X509Chain import X509Chain  # pylint: disable=import-error

# pylint: disable=broad-except


class X509Request(object):
  """
  Class representing X509 Certificate Request. it is used for delegation.
  Please see :ref:`about_proxies` for detailed explanations on delegation,
  and :py:class:`DIRAC.Core.Security.m2crypto.X509Chain` for code examples.

  """

  def __init__(self, reqObj=None, pkeyObj=None):
    """ C'tor

        :param reqObj: M2Crypto.X509.Request object. Never used. Shall be removed
        :param pkeyObj: M2Crypto.EVP.PKey() object. Never used. Shall be removed
    """
    self.__valid = False
    self.__reqObj = reqObj
    self.__pkeyObj = pkeyObj
    if reqObj and pkeyObj:  # isn't it a bit too liberal?
      self.__valid = True

  def generateProxyRequest(self, bitStrength=1024, limited=False):
    """
      Initialize the Request object as well as the PKey.

      :param bitStrength: (default 1024) length of the key generated
      :param limited: (default False) If True, request is done for a limited proxy
    """
    # self.__pkeyObj is both the public and private key
    self.__pkeyObj = M2Crypto.EVP.PKey()
    self.__pkeyObj.assign_rsa(M2Crypto.RSA.gen_key(bitStrength, 65537, callback=M2Crypto.util.quiet_genparam_callback))
    self.__reqObj = M2Crypto.X509.Request()
    self.__reqObj.set_pubkey(self.__pkeyObj)

    if limited:
      self.__reqObj.get_subject().add_entry_by_txt(field="CN", type=M2Crypto.ASN1.MBSTRING_ASC,
                                                   entry="limited proxy", len=-1, loc=-1, set=0)
    else:
      self.__reqObj.get_subject().add_entry_by_txt(field="CN", type=M2Crypto.ASN1.MBSTRING_ASC, entry="proxy",
                                                   len=-1, loc=-1, set=0)
    self.__reqObj.sign(self.__pkeyObj, "sha256")
    self.__valid = True

  def dumpRequest(self):
    """
    Get the request as a string

    :returns: S_OK(pem encoded request)
    """
    if not self.__valid:
      return S_ERROR(DErrno.ENOCERT)
    try:
      reqStr = self.__reqObj.as_pem()
    except Exception as e:
      return S_ERROR(DErrno.EX509, "Can't serialize request: %s" % e)
    return S_OK(reqStr)

  # def getRequestObject(self):
  #   """
  #   Get internal X509Request object
  #   Not used
  #   """
  #   return S_OK(self.__reqObj)

  def getPKey(self):
    """
    Get PKey Internal

    :returns: M2Crypto.EVP.PKEY object
    """
    return self.__pkeyObj

  def dumpPKey(self):
    """
    Get the private as a string

    :returns: S_OK(PEM encoded PKey)
    """
    if not self.__valid:
      return S_ERROR(DErrno.ENOCERT)
    try:
      pkeyStr = self.__pkeyObj.as_pem(cipher=None, callback=M2Crypto.util.no_passphrase_callback)
    except Exception as e:
      return S_ERROR(DErrno.EX509, "Can't serialize pkey: %s" % e)
    return S_OK(pkeyStr)

  def dumpAll(self):
    """
    Dump the Request and the PKey into a string

    :returns: S_OK(PEM encoded req + key), S_ERROR
    """

    if not self.__valid:
      return S_ERROR(DErrno.ENOCERT)

    req = self.dumpRequest()
    pkey = self.dumpPKey()
    if not req['OK']:
      return S_ERROR(DErrno.EX509, "Can't serialize request: %s" % req['Message'])
    if not pkey['OK']:
      return S_ERROR(DErrno.EX509, "Can't serialize pkey: %s" % pkey['Message'])
    return S_OK(b"%s%s" % (req['Value'], pkey['Value']))

  def loadAllFromString(self, pemData):
    """ load the Request and key argument from a PEM encoded string.

        :param pemData: PEN encoded string containing Request and PKey

        :returns: S_OK()
    """
    try:
      self.__reqObj = M2Crypto.X509.load_request_string(pemData)
    except Exception as e:
      return S_ERROR(DErrno.ENOCERT, str(e))
    try:
      self.__pkeyObj = M2Crypto.EVP.load_key_string(pemData)
    except Exception as e:
      return S_ERROR(DErrno.ENOPKEY, str(e))
    self.__valid = True
    return S_OK()

  # def generateChainFromResponse(self, pemData):
  #   """
  #   Generate a X509 Chain from the pkey and the pem data passed as the argument
  #   Return : S_OK( X509Chain ) / S_ERROR
  #   """
  #   if not self.__valid:
  #     return S_ERROR(DErrno.ENOCERT)
  #   chain = X509Chain()
  #   ret = chain.loadChainFromString(pemData)
  #   if not ret['OK']:
  #     return ret
  #   ret = chain.setPKey(self.__pkeyObj)
  #   if not ret['OK']:
  #     return ret
  #   return chain

  def getSubjectDN(self):
    """
    Get subject DN of the request as a string

    :return: S_OK( string )/S_ERROR
    """
    if not self.__valid:
      return S_ERROR(DErrno.ENOCERT)
    return S_OK(str(self.__reqObj.get_subject()))

  def checkChain(self, chain):
    """
    Check that the public keys of the chain and the request match.

    :param chain: :py:class:`X509Chain` object
    """

    if not self.__valid:
      return S_ERROR(DErrno.ENOCERT)
    retVal = chain.getCertInChain()
    if not retVal['OK']:
      return retVal
    lastCert = retVal['Value']
    chainPubKey = lastCert.getPublicKey()
    if not chainPubKey['OK']:
      return chainPubKey

    # as_der will dump public key info, while as_pem
    # dumps private key.
    chainPubKey = chainPubKey['Value'].as_der()
    reqPubKey = self.__reqObj.get_pubkey().as_der()
    if not chainPubKey == reqPubKey:
      return S_ERROR(DErrno.EX509, "Public keys do not match")

    return S_OK(True)
