""" Manage the manifests of a repository. """

from app import label_validator
from flask import request
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
                           RepositoryParamResource, log_action, validate_json_request,
                           path_param, parse_args, query_param, truthy_bool, abort, api)
from endpoints.exception import NotFound
from data import model

from digest import digest_tools

BASE_MANIFEST_ROUTE = '/v1/repository/<apirepopath:repository>/manifest/<regex("{0}"):manifestref>'
MANIFEST_DIGEST_ROUTE = BASE_MANIFEST_ROUTE.format(digest_tools.DIGEST_PATTERN)
ALLOWED_LABEL_MEDIA_TYPES = ['text/plain', 'application/json']

def label_view(label):
  view = {
    'id': label.uuid,
    'key': label.key,
    'value': label.value,
    'source_type': label.source_type.name,
    'media_type': label.media_type.name,
  }

  return view

@resource(MANIFEST_DIGEST_ROUTE + '/labels')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('manifestref', 'The digest of the manifest')
class RepositoryManifestLabels(RepositoryParamResource):
  """ Resource for listing the labels on a specific repository manifest. """
  schemas = {
    'AddLabel': {
      'type': 'object',
      'description': 'Adds a label to a manifest',
      'required': [
        'key',
        'value',
        'media_type',
      ],
      'properties': {
        'key': {
          'type': 'string',
          'description': 'The key for the label',
        },
        'value': {
          'type': 'string',
          'description': 'The value for the label',
        },
        'media_type': {
          'type': ['string'],
          'description': 'The media type for this label',
          'enum': ALLOWED_LABEL_MEDIA_TYPES,
        },
      },
    },
  }

  @require_repo_read
  @nickname('listManifestLabels')
  @parse_args()
  @query_param('filter', 'If specified, only labels matching the given prefix will be returned',
               type=str, default=None)
  def get(self, namespace, repository, manifestref, parsed_args):
    try:
      tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
    except model.DataModelException:
      raise NotFound()

    labels = model.label.list_manifest_labels(tag_manifest, prefix_filter=parsed_args['filter'])
    return {
      'labels': [label_view(label) for label in labels]
    }

  @require_repo_write
  @nickname('addManifestLabel')
  @validate_json_request('AddLabel')
  def post(self, namespace, repository, manifestref):
    """ Adds a new label into the tag manifest. """
    try:
      tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
    except model.DataModelException:
      raise NotFound()

    label_data = request.get_json()

    # Check for any reserved prefixes.
    if label_validator.has_reserved_prefix(label_data['key']):
      abort(400, message='Label has a reserved prefix')

    label = model.label.create_manifest_label(tag_manifest, label_data['key'],
                                              label_data['value'], 'api',
                                              media_type_name=label_data['media_type'])
    metadata = {
      'id': label.uuid,
      'key': label_data['key'],
      'value': label_data['value'],
      'manifest_digest': manifestref,
      'media_type': label_data['media_type'],
    }

    log_action('manifest_label_add', namespace, metadata, repo=tag_manifest.tag.repository)

    resp = {'label': label_view(label)}
    repo_string = '%s/%s' % (namespace, repository)
    headers = {
      'Location': api.url_for(ManageRepositoryManifestLabel, repository=repo_string,
                              manifestref=manifestref, labelid=label.uuid),
    }
    return resp, 201, headers


@resource(MANIFEST_DIGEST_ROUTE + '/labels/<labelid>')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('manifestref', 'The digest of the manifest')
@path_param('labelid', 'The ID of the label')
class ManageRepositoryManifestLabel(RepositoryParamResource):
  """ Resource for managing the labels on a specific repository manifest. """
  @require_repo_read
  @nickname('getManifestLabel')
  @parse_args()
  def get(self, namespace, repository, manifestref, labelid):
    """ Retrieves the label with the specific ID under the manifest. """
    try:
      tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
    except model.DataModelException:
      raise NotFound()

    label = model.label.get_manifest_label(labelid, tag_manifest)
    if label is None:
      raise NotFound()

    return label_view(label)


  @require_repo_write
  @nickname('deleteManifestLabel')
  def delete(self, namespace, repository, manifestref, labelid):
    """ Deletes an existing label from a manifest. """
    try:
      tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
    except model.DataModelException:
      raise NotFound()

    deleted = model.label.delete_manifest_label(labelid, tag_manifest)
    if deleted is None:
      raise NotFound()

    metadata = {
      'id': labelid,
      'key': deleted.key,
      'value': deleted.value,
      'manifest_digest': manifestref
    }

    log_action('manifest_label_delete', namespace, metadata, repo=tag_manifest.tag.repository)
    return 'Deleted', 204