# Copyright 2019-2020 VMware, Inc.
# All rights reserved. -- VMware Confidential

"""Utilities for ImageManager VAPI.
"""

from datetime import datetime

from . import Constants

# Python datetime format that is the closest to, but not exactly matching vAPI's
# definition. See vapi-core/vapi/DateTime.cpp for the full format.
# str2Time() and time2Str() need to be used to convert from/to vAPI's format.
BASE_TASK_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%f'

# Get a list of object.toDict() result for each object in the non-empty list.
# Otherwise simply return None.
getOptionalDictList = lambda x: [i.toDict() for i in x] if x else None

# Get a sorted comma separated output
getCommaSepArg = lambda x: ', '.join(sorted(x))

class Notification(object):
   """A class that represents one VAPI notification.
      See com.vmware.esx.settings_daemon.Notifications.
   """
   def __init__(self, notificationId, msgId, msg, resMsgId, resMsg,
                msgArgs=None, resArgs=None):
      self.notificationId = notificationId
      self.msgId = msgId
      self.msg = msg
      self.msgArgs = msgArgs or []
      self.resMsgId = resMsgId
      self.resMsg = resMsg
      self.resArgs = resArgs or []
      self.time = datetime.utcnow()

   def toDict(self):
      msg = dict(id=self.msgId,
                 default_message=self.msg,
                 args=self.msgArgs)
      if self.resMsgId or self.resMsg or self.resArgs:
         # Resolution is optional.
         resolution = dict(id=self.resMsgId,
                           default_message=self.resMsg,
                           args=self.resArgs)
      else:
         resolution = None
      return dict(id=self.notificationId,
                  message=msg,
                  resolution=resolution,
                  time=time2Str(self.time))

class Notifications(object):
   """A collection of notifications divided to info, warning and error
      categories.
      See com.vmware.esx.settings_daemon.Notifications.
   """
   def __init__(self, infoMsgs=None, warnMsgs=None, errMsgs=None):
      self.info = infoMsgs or []
      self.warnings = warnMsgs or []
      self.errors = errMsgs or []

   def toDict(self):
      return dict(info=getOptionalDictList(self.info),
                  warnings=getOptionalDictList(self.warnings),
                  errors=getOptionalDictList(self.errors))

def time2Str(timeObj):
   """Convert datetime object to a VAPI time string.
   """
   # Truncate microsec to millisec and add Z.
   return timeObj.strftime(BASE_TASK_TIME_FORMAT)[:-3] + 'Z'

def getFormattedMessage(msg, args):
   """Format a message for VAPI.
   """
   if args:
      # Messages in Constants are copied from VLCM on VC, they have positional
      # arguments that start from {1} rather than {0}. An extra argument will
      # save the effort of having two slightly different messages.
      return msg.format(*([''] + args))
   return msg

def getExceptionNotification(ex):
   """Get a notification from an exception.
   """
   UNKNOWN_ERR = 'UnknownError'

   def getMappedErrorName(ex):
      """Get mapped name of the error.
      """
      # Error name is figured using the alias map and the conversion map.
      # Aliases unify similar errors, and during conversion, UnknownError is
      # assigned for an error that is not explicitly handled.
      exType = type(ex).__name__
      errorAlias = Constants.ESXIMAGE_ERROR_ALIAS.get(exType, exType)
      errorName = (errorAlias if errorAlias in Constants.ESXIMAGE_ERROR_MSG_ARG
                   else UNKNOWN_ERR)
      return errorName

   errorName = getMappedErrorName(ex)

   if hasattr(ex, 'cause') and ex.cause is not None:
      # Nested exception, get notification from the actual error if it is not
      # mapped to unknown error.
      causeErrorName = getMappedErrorName(ex.cause)
      if causeErrorName != UNKNOWN_ERR:
         errorName = causeErrorName
         ex = ex.cause

   notifId = Constants.ESXIMAGE_PREFIX + errorName
   msg, argNames = Constants.ESXIMAGE_ERROR_MSG_ARG[errorName]

   # Get arguments for the notification by attributes in the exception
   # object.
   msgArgs = []
   for arg in argNames:
      attr = getattr(ex, arg)
      if isinstance(attr, list):
         msgArgs.append(','.join(attr))
      else:
         msgArgs.append(str(attr))
   msg = getFormattedMessage(msg, msgArgs)

   # For error reporting, resolution is not used.
   return Notification(notifId, notifId, msg, "", "", msgArgs=msgArgs)
