from abc import ABCMeta, abstractmethod
from collections import namedtuple

from six import add_metaclass


class BlobDescriptor(namedtuple('Blob', ['mediaType', 'size', 'digest', 'urls'])):
  """ BlobDescriptor describes a blob with its mediatype, size and digest.
      A BlobDescriptor is used to retrieves the actual blob.
  """


class ChannelReleasesView(namedtuple('ChannelReleasesView', ['name', 'current', 'releases'])):
  """ A channel is a pointer to a Release (current).
      Releases are the previous tags pointed by channel (history).
  """


class ChannelView(namedtuple('ChannelView', ['name', 'current'])):
  """ A channel is a pointer to a Release (current).
  """


class ApplicationSummaryView(
    namedtuple('ApplicationSummaryView', [
      'name', 'namespace', 'visibility', 'default', 'manifests', 'channels', 'releases',
      'updated_at', 'created_at'
    ])):
  """ ApplicationSummaryView is an aggregated view of an application repository.
  """


class ApplicationManifest(namedtuple('ApplicationManifest', ['mediaType', 'digest', 'content'])):
  """ ApplicationManifest embed the BlobDescriptor and some metadata around it.
      An ApplicationManifest is content-addressable.
  """


class ApplicationRelease(
    namedtuple('ApplicationRelease', ['release', 'name', 'created_at', 'manifest'])):
  """ The ApplicationRelease associates an ApplicationManifest to a repository and release.
  """


@add_metaclass(ABCMeta)
class AppRegistryDataInterface(object):
  """ Interface that represents all data store interactions required by a App Registry.
  """

  @abstractmethod
  def list_applications(self, namespace=None, media_type=None, search=None, username=None,
                        with_channels=False):
    """ Lists all repositories that contain applications, with optional filtering to a specific
        namespace and/or to those visible to a specific user.

       Returns: list of ApplicationSummaryView
    """
    pass

  @abstractmethod
  def application_is_public(self, package_name):
    """
      Returns true if the application is public
    """
    pass

  @abstractmethod
  def create_application(self, package_name, visibility, owner):
    """ Create a new app repository, owner is the user who creates it """
    pass

  @abstractmethod
  def application_exists(self, package_name):
    """ Returns true if the application exists """
    pass

  @abstractmethod
  def basic_search(self, query, username=None):
    """ Returns an array of matching application in the format: 'namespace/name'
    Note:
      * Only 'public' repositories are returned
    """
    pass

  # @TODO: Paginate
  @abstractmethod
  def list_releases(self, package_name, media_type=None):
    """ Returns the list of all releases(names) of an AppRepository
    Example:
        >>> get_app_releases('ant31/rocketchat')
        ['1.7.1', '1.7.0', '1.7.2']
    """
    pass

  # @TODO: Paginate
  @abstractmethod
  def list_manifests(self, package_name, release=None):
    """ Returns the list of all available manifests type of an Application across all releases or
    for a specific one.

    Example:
        >>> get_app_releases('ant31/rocketchat')
        ['1.7.1', '1.7.0', '1.7.2']
    """
    pass

  @abstractmethod
  def fetch_release(self, package_name, release, media_type):
    """
     Returns an ApplicationRelease
    """
    pass

  @abstractmethod
  def store_blob(self, cnrblob, content_media_type):
    """
    Upload the blob content to a storage location and creates a Blob entry in the DB.

    Returns a BlobDescriptor
    """
    pass

  @abstractmethod
  def create_release(self, package, user, visibility, force=False):
    """ Creates and returns an ApplicationRelease
    - package is a data.model.Package object
    - user is the owner of the package
    - visibility is a string: 'public' or 'private'
    """
    pass

  @abstractmethod
  def release_exists(self, package, release):
    """ Return true if a release with that name already exist or
    has existed (including deleted ones)
    """
    pass

  @abstractmethod
  def delete_release(self, package_name, release, media_type):
    """ Remove/Delete an app-release from an app-repository.
        It does not delete the entire app-repository, only a single release
    """
    pass

  @abstractmethod
  def list_release_channels(self, package_name, release, active=True):
    """ Returns a list of Channel that are/was pointing to a release.
        If active is True, returns only active Channel (lifetime_end not null)
    """
    pass

  @abstractmethod
  def channel_exists(self, package_name, channel_name):
    """ Returns true if the channel with the given name exists under the matching package """
    pass

  @abstractmethod
  def update_channel(self, package_name, channel_name, release):
    """ Append a new release to the Channel
    Returns a new Channel with the release as current
    """
    pass

  @abstractmethod
  def delete_channel(self, package_name, channel_name):
    """ Delete a Channel, it doesn't delete/touch the ApplicationRelease pointed by the channel """

  # @TODO: Paginate
  @abstractmethod
  def list_channels(self, package_name):
    """ Returns all AppChannel for a package """
    pass

  @abstractmethod
  def fetch_channel(self, package_name, channel_name, with_releases=True):
    """ Returns an Channel
    Raises: ChannelNotFound, PackageNotFound
    """
    pass

  @abstractmethod
  def log_action(self, event_name, namespace_name, repo_name=None, analytics_name=None,
                 analytics_sample=1, **kwargs):
    """ Logs an action to the audit log. """
    pass

  @abstractmethod
  def get_blob_locations(self, digest):
    """ Returns a list of strings for the locations in which a Blob is present. """
    pass