""" :mod: GFAL2_SRM2Storage

    =================

    .. module: python

    :synopsis: SRM2 module based on the GFAL2_StorageBase class.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# pylint: disable=invalid-name

import six
import errno
import json

import gfal2  # pylint: disable=import-error

# from DIRAC
from DIRAC import gLogger, gConfig, S_OK, S_ERROR
from DIRAC.Resources.Storage.GFAL2_StorageBase import GFAL2_StorageBase
from DIRAC.Resources.Storage.Utilities import checkArgumentFormat


__RCSID__ = "$Id$"


class GFAL2_SRM2Storage(GFAL2_StorageBase):
  """ SRM2 SE class that inherits from GFAL2StorageBase
  """

  _INPUT_PROTOCOLS = ['file', 'root', 'srm', 'gsiftp']
  _OUTPUT_PROTOCOLS = ['file', 'root', 'dcap', 'gsidcap', 'rfio', 'srm', 'gsiftp']

  def __init__(self, storageName, parameters):
    """ """
    super(GFAL2_SRM2Storage, self).__init__(storageName, parameters)
    self.log = gLogger.getSubLogger("GFAL2_SRM2Storage", True)
    self.log.debug("GFAL2_SRM2Storage.__init__: Initializing object")
    self.pluginName = 'GFAL2_SRM2'

    # This attribute is used to know the file status (OFFLINE,NEARLINE,ONLINE)
    self._defaultExtendedAttributes = ['user.status']

    # ##
    #    Setting the default SRM parameters here. For methods where this
    #    is not the default there is a method defined in this class, setting
    #    the proper values and then calling the base class method.
    # ##

    self.gfal2requestLifetime = gConfig.getValue('/Resources/StorageElements/RequestLifeTime', 100)

    self.__setSRMOptionsToDefault()

    # This lists contains the list of protocols to ask to SRM to get a URL
    # It can be either defined in the plugin of the SE, or as a global option
    if 'ProtocolsList' in parameters:
      self.protocolsList = parameters['ProtocolsList'].split(',')
    else:
      self.log.debug("GFAL2_SRM2Storage: No protocols provided, using the default protocols.")
      self.protocolsList = self.defaultLocalProtocols
      self.log.debug('GFAL2_SRM2Storage: protocolsList = %s' % self.protocolsList)

  def __setSRMOptionsToDefault(self):
    ''' Resetting the SRM options back to default

    '''
    self.ctx.set_opt_integer("SRM PLUGIN", "OPERATION_TIMEOUT", self.gfal2Timeout)
    if self.spaceToken:
      self.ctx.set_opt_string("SRM PLUGIN", "SPACETOKENDESC", self.spaceToken)
    self.ctx.set_opt_integer("SRM PLUGIN", "REQUEST_LIFETIME", self.gfal2requestLifetime)
    # Setting the TURL protocol to gsiftp because with other protocols we have authorisation problems
#    self.ctx.set_opt_string_list( "SRM PLUGIN", "TURL_PROTOCOLS", self.defaultLocalProtocols )
    self.ctx.set_opt_string_list("SRM PLUGIN", "TURL_PROTOCOLS", ['gsiftp'])

  def _updateMetadataDict(self, metadataDict, attributeDict):
    """ Updating the metadata dictionary with srm specific attributes

    :param self: self reference
    :param dict: metadataDict we want add the SRM specific attributes to
    :param dict: attributeDict contains 'user.status' which we then fill in the metadataDict

    """
    # 'user.status' is the extended attribute we are interested in
    user_status = attributeDict.get('user.status', '')
    metadataDict['Cached'] = int('ONLINE' in user_status)
    metadataDict['Migrated'] = int('NEARLINE' in user_status)
    metadataDict['Lost'] = int(user_status == 'LOST')
    metadataDict['Unavailable'] = int(user_status == 'UNAVAILABLE')
    metadataDict['Accessible'] = not metadataDict['Lost'] and metadataDict['Cached'] and not metadataDict['Unavailable']

  def getTransportURL(self, path, protocols=False):
    """ obtain the tURLs for the supplied path and protocols

    :param self: self reference
    :param str path: path on storage
    :param mixed protocols: protocols to use
    :returns: Failed dict {path : error message}
             Successful dict {path : transport url}
             S_ERROR in case of argument problems
    """
    res = checkArgumentFormat(path)
    if not res['OK']:
      return res
    urls = res['Value']

    self.log.debug(
        'GFAL2_SRM2Storage.getTransportURL: Attempting to retrieve tURL for %s paths' %
        len(urls))

    failed = {}
    successful = {}
    if not protocols:
      listProtocols = self.protocolsList
      if not listProtocols:
        return S_ERROR(
            "GFAL2_SRM2Storage.getTransportURL: No local protocols defined and no defaults found.")
    elif isinstance(protocols, six.string_types):
      listProtocols = [protocols]
    elif isinstance(protocols, list):
      listProtocols = protocols
    else:
      return S_ERROR("getTransportURL: Must supply desired protocols to this plug-in.")

    # Compatibility because of castor returning a castor: url if you ask
    # for a root URL, and a root: url if you ask for a xroot url...
    if 'root' in listProtocols and 'xroot' not in listProtocols:
      listProtocols.insert(listProtocols.index('root'), 'xroot')
    elif 'xroot' in listProtocols and 'root' not in listProtocols:
      listProtocols.insert(listProtocols.index('xroot') + 1, 'root')

    if self.protocolParameters['Protocol'] in listProtocols:
      successful = {}
      failed = {}
      for url in urls:
        if self.isURL(url)['Value']:
          successful[url] = url
        else:
          failed[url] = 'getTransportURL: Failed to obtain turls.'

      return S_OK({'Successful': successful, 'Failed': failed})

    for url in urls:
      res = self.__getSingleTransportURL(url, listProtocols)
      self.log.debug('res = %s' % res)

      if not res['OK']:
        failed[url] = res['Message']
      else:
        successful[url] = res['Value']

    return S_OK({'Failed': failed, 'Successful': successful})

  def __getSingleTransportURL(self, path, protocols=False):
    """ Get the tURL from path with getxattr from gfal2

    :param self: self reference
    :param str path: path on the storage
    :returns: S_OK( Transport_URL ) in case of success
              S_ERROR( errStr ) in case of a failure
    """
    self.log.debug(
        'GFAL2_SRM2Storage.__getSingleTransportURL: trying to retrieve tURL for %s' %
        path)
    if protocols:
      self.ctx.set_opt_string_list("SRM PLUGIN", "TURL_PROTOCOLS", protocols)

    res = self._getExtendedAttributes(path, attributes=['user.replicas'])
    self.__setSRMOptionsToDefault()

    if res['OK']:
      return S_OK(res['Value']['user.replicas'])

    errStr = 'GFAL2_SRM2Storage.__getSingleTransportURL: Extended attribute tURL is not set.'
    self.log.debug(errStr, res['Message'])
    return res

  def getOccupancy(self, *parms, **kws):
    """ Gets the GFAL2_SRM2Storage occupancy info.

      TODO: needs gfal2.15 because of bugs:
      https://its.cern.ch/jira/browse/DMC-979
      https://its.cern.ch/jira/browse/DMC-977

      It queries the srm interface for a given space token.
      Out of the results, we keep totalsize, guaranteedsize, and unusedsize all in B.
    """

    if not self.spaceToken:
      self.log.info("getOccupancy: SpaceToken not defined for this SE. Falling back to the default getOccupancy.")
      return super(GFAL2_SRM2Storage, self).getOccupancy(*parms, **kws)

    # Gfal2 extended parameter name to query the space token occupancy
    spaceTokenAttr = 'spacetoken.description?%s' % self.protocolParameters['SpaceToken']
    # gfal2 can take any srm url as a base.
    spaceTokenEndpoint = self.getURLBase(withWSUrl=True)['Value']
    try:
      occupancyStr = self.ctx.getxattr(spaceTokenEndpoint, spaceTokenAttr)
      try:
        occupancyDict = json.loads(occupancyStr)[0]
      except ValueError:
        # https://its.cern.ch/jira/browse/DMC-977
        # a closing bracket is missing, so we retry after adding it
        occupancyStr = occupancyStr[:-1] + '}]'
        occupancyDict = json.loads(occupancyStr)[0]

        # https://its.cern.ch/jira/browse/DMC-979
        # We set totalsize to guaranteed size
        # (it is anyway true for all the SEs I could test)
        occupancyDict['totalsize'] = occupancyDict.get('guaranteedsize', 0)

    except (gfal2.GError, ValueError) as e:
      errStr = 'Something went wrong while checking for spacetoken occupancy.'
      self.log.verbose(errStr, e.message)
      return S_ERROR(getattr(e, 'code', errno.EINVAL), "%s %s" % (errStr, repr(e)))

    sTokenDict = {}

    sTokenDict['Total'] = float(occupancyDict.get('totalsize', '0'))
    sTokenDict['Free'] = float(occupancyDict.get('unusedsize', '0'))
    sTokenDict['SpaceReservation'] = self.protocolParameters['SpaceToken']

    return S_OK(sTokenDict)
