########################################################################
# Copyright (C) 2021 VMware, Inc.                                      #
# All Rights Reserved                                                  #
########################################################################
#

"""
Utility functions to get release object info from cached depots.

This is the python side code to generate the release object info defined in
   vum/sysimage/integrity/lib/public/depotInfo.

"""
import logging

from ..Bulletin import ComponentCollection

log = logging.getLogger(__name__)

BASEIMAGE_UI_NAME = 'ESXi'

# Common field names for relase objects.
DESCRIPTION = "description"
DISPLAY_NAME = "display_name"
DISPLAY_VERSION = "display_version"
DOC_URL = "docURL"
RELEASE_DATE = "release_date"
SUMMARY = "summary"
VERSION = "version"

# This is for release units other than solution.
CATEGORY = "category"

# Field names for release object other than base image.
NAME = "name"
VENDOR = "vendor"

# Field names for addon and manifest.
SUPPORTED_BASE_IMAGE_VERSIONS = "supported_baseimage_versions"

# Field names for manifest.
HSM_NAME = "hsm_name"
HSP_NAME = "hsp_name"
HSP_VERSION = "hsp_version"

# Field names for component/bulletin.
BULLETIN_ID = "bulletin_id"
CONTACT = "contact"
IS_INDEPENDENT = "is_independent"
URGENCY = "urgency"

# Component type is a place holder now; supported types are DRIVER and SOLUTION.
# This field is not used in business logic yet. Need improve to cover all
# component types.
TYPE = "type"

# Field names for VIB.
VIB_ID = "vib_id"

COMMON_FIELD_NAME = [DESCRIPTION, DISPLAY_NAME, DISPLAY_VERSION, DOC_URL,
                     RELEASE_DATE, SUMMARY, VERSION]

ADDON_FIELD_NAME = COMMON_FIELD_NAME + \
                   [SUPPORTED_BASE_IMAGE_VERSIONS, CATEGORY, NAME, VENDOR]

BASE_IMAGE_FIELD_NAME = COMMON_FIELD_NAME + [CATEGORY]

COMPONENT_FIELD_NAME = COMMON_FIELD_NAME + [BULLETIN_ID, CATEGORY, CONTACT,
                          IS_INDEPENDENT, NAME, TYPE, URGENCY, VENDOR]

MANIFEST_FIELD_NAME = ADDON_FIELD_NAME + [HSM_NAME, HSP_NAME, HSP_VERSION]

SOLUTION_FIELD_NAME = COMMON_FIELD_NAME + [NAME, VENDOR]

VIB_FIELD_NAME = [VIB_ID]

def getDisplayName(x):
   """ Get the display name of a release object.
   """
   try:
      return x.nameSpec.uiString
   except AttributeError:
      try:
         return x.compNameUiStr
      except AttributeError:
         return BASEIMAGE_UI_NAME

def getDisplayVersion(x):
   """ Get the display version of a release object.
   """
   try:
      return x.versionSpec.uiString
   except AttributeError:
      return x.compVersionUiStr

def getName(x):
   """ Get the name of a release object.
   """
   try:
      return x.nameSpec.name
   except AttributeError:
      return x.compNameStr

def getVersion(x):
   """ Get the version of a release object.
   """
   try:
      return x.versionSpec.version.versionstring
   except AttributeError:
      return x.compVersionStr

def getReleaseDate(x):
   """ Get the release date of a release object.
   """
   try:
      return x.releaseDate
   except AttributeError:
      return x.releasedate

def getDocURL(x):
   """ Get the doc URL of a release object.
   """
   try:
      return x.docURL
   except AttributeError:
      return x.kburl

def getID(x):
   """ Get the identifier of the release object from its info dict.
   """
   theID = x.get(VIB_ID, None)
   if theID is None:
      theID = x.get(BULLETIN_ID, None)
      if theID is None:
         name = x.get(NAME, BASEIMAGE_UI_NAME)
         version = x.get(VERSION, '')
         if name == BASEIMAGE_UI_NAME and not version:
            log.warning('Invalid release object info %s', str(x))
         theID = name + '_' + version
   return theID

# The map from field name to the getter function.
FIELD_GETTER_MAP = {
   SUPPORTED_BASE_IMAGE_VERSIONS: lambda x: x.supportedBaseImageVersions,
   BULLETIN_ID: lambda x: x.id,
   CATEGORY: lambda x: x.category,
   CONTACT: lambda x: x.contact,
   DESCRIPTION: lambda x: x.description,
   DISPLAY_NAME: getDisplayName,
   DISPLAY_VERSION: getDisplayVersion,
   DOC_URL: getDocURL,
   HSM_NAME: lambda x: x.hardwareSupportInfo.manager.name,
   HSP_NAME: lambda x: x.hardwareSupportInfo.package.name,
   HSP_VERSION: lambda x: x.hardwareSupportInfo.package.version,
   IS_INDEPENDENT: lambda x: True,
   NAME: getName,
   RELEASE_DATE: getReleaseDate,
   SUMMARY: lambda x: x.summary,
   TYPE: lambda x: 'DRIVER',
   URGENCY: lambda x: x.urgency,
   VENDOR: lambda x: x.vendor,
   VERSION: getVersion,
   VIB_ID: lambda x: x.id
}

# Release object type names
ADDON = 'addon'
BASEIMAGE = 'baseimage'
BULLETIN = 'bulletin'
COMPONENT = 'component'
MANIFEST = 'manifest'
SOLUTION= 'solution'
VIB = 'vib'

# The map from release object type to release object field name list.
RELEASE_OBJECT_TYPE_FIELD_MAP = {
   ADDON: ADDON_FIELD_NAME,
   BASEIMAGE: BASE_IMAGE_FIELD_NAME,
   COMPONENT: COMPONENT_FIELD_NAME,
   MANIFEST: MANIFEST_FIELD_NAME,
   SOLUTION: SOLUTION_FIELD_NAME,
   VIB: VIB_FIELD_NAME
}

# Use plural of the release object type name in the result dictionary.
plural = lambda x: x + 's'

def GetDepotInfo(depotCollection, depotUrls):
   """ Get the release object info for the provided depots by url from
       a depot collection.
   """
   depotsInfoMap = {url: None for url in depotUrls}
   for url in depotUrls:
      depotInfo = {plural(t): list()
                   for t in RELEASE_OBJECT_TYPE_FIELD_MAP}
      releaseObjects = depotCollection.GetReleaseObjects(url)

      # Depot side only has bulletins.
      releaseObjects[COMPONENT] = releaseObjects[BULLETIN]

      # Collection of the components belongs to release units.
      dependentComponents = list()

      for rot in RELEASE_OBJECT_TYPE_FIELD_MAP:
         relObjColl = releaseObjects[rot]
         relInfoColl = depotInfo[plural(rot)]
         for relObj in relObjColl.values():
            if rot in (ADDON, BASEIMAGE, MANIFEST):
               dependentComponents.extend(relObj.components.items())
            relInfo = dict()
            for field in RELEASE_OBJECT_TYPE_FIELD_MAP[rot]:
               relInfo[field] = FIELD_GETTER_MAP[field](relObj)
            relInfoColl.append(relInfo)

      # Collection of the solution components.
      solutionComps = {}
      compColl = ComponentCollection(releaseObjects[COMPONENT], True)
      for sol in releaseObjects[SOLUTION].values():
         solCompDict = sol.MatchComponents(compColl)
         for name, comps in solCompDict.items():
            for comp in comps:
               version = comp.compVersionStr
               solutionComps.setdefault(name, []).append(version)

      # Mark components in release units as dependent.
      for comp in depotInfo[plural(COMPONENT)]:
          name = comp[NAME]
          version = comp[VERSION]
          if name in solutionComps and version in solutionComps[name]:
             comp[TYPE] = 'SOLUTION'
             comp[IS_INDEPENDENT] = False
          elif (name, version) in dependentComponents:
             comp[IS_INDEPENDENT] = False
      depotsInfoMap[url] = depotInfo
   return depotsInfoMap


def _GetDepotReleaseObjectsInfo(depotCollection, depotUrls):
   """ Get info of the distinctive release objects for the provided depots
      by URL from a depot collection.
   """

   depotsInfoMap = GetDepotInfo(depotCollection, depotUrls)
   depotInfoDicts = {plural(t): dict()
                     for t in RELEASE_OBJECT_TYPE_FIELD_MAP}
   for tmpDepotInfo in depotsInfoMap.values():
      for relObjType, relObjList in tmpDepotInfo.items():
         depotInfoDict = depotInfoDicts[relObjType]
         for relObj in relObjList:
            depotInfoDict[getID(relObj)] = relObj

   return depotInfoDicts


def GetDepotUniqueInfo(depotCollection, depotUrls):
   """ Get info of release objects that are unique to the provided depots, from
       a depot collection.
   """

   allDepotURLs = depotCollection.GetDepotURLs()
   complementaryDepotURLs = list(set(allDepotURLs) - set(depotUrls))
   depotInfoDicts1 = _GetDepotReleaseObjectsInfo(depotCollection, depotUrls)
   depotInfoDicts2 = _GetDepotReleaseObjectsInfo(depotCollection,
                                                       complementaryDepotURLs)
   depotInfoResult = dict()
   for relObjType in depotInfoDicts1:
      relObjDict1 = depotInfoDicts1[relObjType]
      relObjDict2 = depotInfoDicts2[relObjType]
      uniqueKeys = set(relObjDict1.keys()) - set(relObjDict2.keys())
      depotInfoResult[relObjType] = [relObjDict1[k] for k in uniqueKeys]

   return depotInfoResult

