'''
VCDB helper database methods
'''
import logging
import base64
import sys
from subprocess import Popen, PIPE
from os import path

from vcdb.const import (
   XML_CFG_FILE, SYMKEY_FILE, VPXD_CFG_FILE, PYTHON, OPEN_SSL, PATCH_OUT_LOG,
   PATCH_ERR_LOG, RUI_KEY_FILE, LIBODBC_SO_2, DBC_UPGRADE_BINARY, DBC_UPGRADE_FOLDER,
   LD_PATH)

import os_utils # pylint: disable=E0401
from l10n import msgMetadata as _T, localizedString as _ # pylint: disable=E0401
from patch_errors import PermanentError # pylint: disable=E0401

PY2 = sys.version_info[0] == 2

LOGGER = logging.getLogger(__name__)

LOGGING_DIR = '/var/log/vmware/applmgmt'
LOG_OUT = path.join(LOGGING_DIR, PATCH_OUT_LOG)
LOG_ERR = path.join(LOGGING_DIR, PATCH_ERR_LOG)

def get_db_user_linux():
   '''
   Get vc database user name from source vpxd.cfg

   @return: user name
   '''
   pipe1 = Popen([XML_CFG_FILE, "-f", VPXD_CFG_FILE, "get", "/config/registry/DB/key_2"],
                 stdout=PIPE)
   db_user = pipe1.communicate()[0].strip()
   pipe1.stdout.close()
   return db_user

def get_db_pass_linux_ruikey():
   '''
   Get vc database user password from vpxd.cfg using rui.key
   This implementation is used for vCSA 5.5 source.

   @return: password
   '''
   key3_cmd = [PYTHON, XML_CFG_FILE,
               "-f", VPXD_CFG_FILE,
               "get", "/config/registry/DB/key_3"]
   py_cmd = "import sys\n"\
            "import base64\n"\
            "data = sys.stdin.read()\n"\
            "data = data[1:]\n"\
            "decBase64 = base64.b64decode(data)\n"\
            "data_len = len(decBase64)\n"\
            "for i in range(data_len):\n"\
            "\n\tsys.stdout.write(decBase64[data_len-i-1])"
   ssl_cmd = [OPEN_SSL, "rsautl", "-inkey", RUI_KEY_FILE, "-pkcs", "-decrypt"]

   pipe1 = Popen(key3_cmd, stdout=PIPE)
   pipe2 = Popen([PYTHON, "-c", py_cmd], stdin=pipe1.stdout, stdout=PIPE)
   pipe1.stdout.close()
   pipe3 = Popen(ssl_cmd, stdin=pipe2.stdout, stdout=PIPE)
   pipe2.stdout.close()
   db_pass = pipe3.communicate()[0].strip()
   pipe3.stdout.close()
   return db_pass

def get_db_pass_symkey():
   '''
   Get vc database user password from vpxd.cfg using symkey.dat
   This implementation is used for vCSA 5.5 source.

   @return: password
   '''
   db_pass = ""
   key3_cmd = [XML_CFG_FILE, "-f", VPXD_CFG_FILE, "get", "/config/registry/DB/key_3"]

   key3_val = os_utils.getCommandOutput(key3_cmd).strip()[1:]
   db_pass = decrypt_key3(get_symkey(), key3_val)
   return db_pass

def decrypt_key3(symkey_hexstring, encrypted_base64):
   '''
   Decrypt password using symkey and encrypted value.

   @symkey_hexstring: symkey.dat contents
   @encrypted_base64: stripped key_3 value from vpxd.cfg
   @return: decrypted password
   '''
   from Crypto.Cipher import AES # pylint: disable=F0401

   # On Python2 you can decode strings as hex, on Py3+ bytes has that option
   if PY2:
      symkey = symkey_hexstring.decode('hex')
   else:
      symkey = bytes.fromhex(symkey_hexstring)
   encrypted_raw = base64.b64decode(encrypted_base64)
   assert (len(encrypted_raw) > 16), "Wrong encrypted string length."
   ciphertext = encrypted_raw[16:]
   ivs = encrypted_raw[0:16]
   cipher = AES.new(symkey, AES.MODE_CBC, ivs)
   try:
      padded_plaintext = cipher.decrypt(ciphertext)
   except Exception as exc:
      LOGGER.error("Failed to decrypt password: %s", exc)
      error = _(_T('vcdb.error.decrypt.password', "Failed to decrypt password: %s"),
                exc)
      raise PermanentError(cause=error)
   #padded_plaintext in Py3+ is bytes thus -1 is a number, in Py2 need to cast it to one
   padding_len = padded_plaintext[-1]
   if PY2:
      padding_len = ord(padding_len)
   plaintext = padded_plaintext[:-padding_len]
   return plaintext

def get_symkey():
   '''
   Read encryption hex string from symkey.dat

   @return: encryption key
   '''
   with open(SYMKEY_FILE) as fhd:
      content = fhd.readlines()
   content_str = content[0].strip()
   symkey_length = len(content_str)
   if symkey_length != 64:
      LOGGER.error("Unexpected symkey length: %d", symkey_length)
      error = _(_T('vcdb.error.symkey.length', "Unexpected symkey length: %d"),
                symkey_length)
      raise PermanentError(cause=error)
   return content_str

def log_std(std, file_path):
   '''
   Write stdout/err output into log file.

   @std: stdout/err output.
   @file_path: log file path
   '''
   with open(file_path, "a+") as fhd:
      for line in std.splitlines():
         fhd.write("%s\n" % line)

def patch_db(vc_user, vc_pass, source_vc_version):
   '''
   Run dbcupgrade tool to patch DB
   '''

   if not path.islink(LIBODBC_SO_2):
      cmd = ["ln", "-s", "/usr/lib64/libodbc.so.1", LIBODBC_SO_2]
      ret = os_utils.getCommandExitCode(cmd)
      if ret != 0:
         LOGGER.error("Error during execution: %s", cmd)
         error = _(_T('vcdb.db.error.link', 'Error during link libodbc.so.1.'))
         sugg_action = _(_T('vcdb.db.resolution.link',
                            'Check that libodbc.so.1 exists.'))
         raise PermanentError(cause=error, resolution=sugg_action)

   cmd_patch = [DBC_UPGRADE_BINARY,
                "-u", base64.b64encode(vc_user),
                "-p", base64.b64encode(vc_pass),
                "-b", source_vc_version,
                "-s", "0",
                "-y",
                "-t", "postgresql"]

   stdout, stderr, ret = os_utils.executeCommand(cmd_patch,
                                                 cwd=DBC_UPGRADE_FOLDER,
                                                 env={"LD_LIBRARY_PATH":LD_PATH})
   log_std(stdout, LOG_OUT)
   log_std(stderr, LOG_ERR)

   if ret != 0:
      LOGGER.error("ERROR dbcupgrade")
      error = _(_T('vcdb.db.error.text',
                   'Error during patch VCDB. See vcdb_patch.err.'))
      sugg_action = _(_T('vcdb.db.resolution.text',
                         'Patch DB is failed. Please check vcdb_patch.log'))
      raise PermanentError(cause=error, resolution=sugg_action)

   try:
      cmd = ["unlink", LIBODBC_SO_2]
      os_utils.getCommandExitCode(cmd)
   except Exception: # pylint: disable=W0703
      LOGGER.info("Error during unlink %s", LIBODBC_SO_2)
