import logging
import json

from flask import request

from app import app
from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
                           require_repo_read, require_repo_write, validate_json_request,
                           ApiResource, internal_only, format_date, api, Unauthorized, NotFound)
from endpoints.common import start_build
from endpoints.trigger import BuildTrigger
from data import model
from auth.permissions import ModifyRepositoryPermission


logger = logging.getLogger(__name__)
user_files = app.config['USERFILES']
build_logs = app.config['BUILDLOGS']


def trigger_view(trigger):
  if trigger and trigger.uuid:  
    config_dict = json.loads(trigger.config)
    build_trigger = BuildTrigger.get_trigger_for_service(trigger.service.name)
    return {
      'service': trigger.service.name,
      'config': config_dict,
      'id': trigger.uuid,
      'connected_user': trigger.connected_user.username,
      'is_active': build_trigger.is_active(config_dict)
    }

  return None


def build_status_view(build_obj, can_write=False):
  status = build_logs.get_status(build_obj.uuid)
  logger.debug('Can write: %s job_config: %s', can_write, build_obj.job_config)
  resp = {
    'id': build_obj.uuid,
    'phase': build_obj.phase,
    'started': format_date(build_obj.started),
    'display_name': build_obj.display_name,
    'status': status,
    'job_config': json.loads(build_obj.job_config) if can_write else None,
    'is_writer': can_write,
    'trigger': trigger_view(build_obj.trigger),
    'resource_key': build_obj.resource_key,
  }

  if can_write:
    resp['archive_url'] = user_files.get_file_url(build_obj.resource_key)

  return resp


@resource('/v1/repository/<path:repository>/build/')
class RepositoryBuildList(RepositoryParamResource):
  """ Resource related to creating and listing repository builds. """
  schemas = {
    'RepositoryBuildRequest': {
      'id': 'RepositoryBuildRequest',
      'type': 'object',
      'description': 'Description of a new repository build.',
      'required': [
        'file_id',
      ],
      'properties': {
        'file_id': {
          'type': 'string',
          'description': 'The file id that was generated when the build spec was uploaded',
        },
        'subdirectory': {
          'type': 'string',
          'description': 'Subdirectory in which the Dockerfile can be found',
        },
      },
    },
  }

  @require_repo_read
  @parse_args
  @query_param('limit', 'The maximum number of builds to return', type=int, default=5)
  @nickname('getRepoBuilds')
  def get(self, args, namespace, repository):
    """ Get the list of repository builds. """
    limit = args['limit']
    builds = list(model.list_repository_builds(namespace, repository, limit))

    can_write = ModifyRepositoryPermission(namespace, repository).can()
    return {
      'builds': [build_status_view(build, can_write) for build in builds]
    }

  @require_repo_write
  @nickname('requestRepoBuild')
  @validate_json_request('RepositoryBuildRequest')
  def post(self, namespace, repository):
    """ Request that a repository be built and pushed from the specified input. """
    logger.debug('User requested repository initialization.')
    request_json = request.get_json()

    dockerfile_id = request_json['file_id']
    subdir = request_json['subdirectory'] if 'subdirectory' in request_json else ''

    # Check if the dockerfile resource has already been used. If so, then it
    # can only be reused if the user has access to the repository for which it
    # was used.
    associated_repository = model.get_repository_for_resource(dockerfile_id)
    if associated_repository:
      if not ModifyRepositoryPermission(associated_repository.namespace,
                                        associated_repository.name):
        raise Unauthorized()

    # Start the build.
    repo = model.get_repository(namespace, repository)
    display_name = user_files.get_file_checksum(dockerfile_id)

    build_request = start_build(repo, dockerfile_id, ['latest'], display_name, subdir, True)

    resp = build_status_view(build_request, True)
    repo_string = '%s/%s' % (namespace, repository)
    headers = {
      'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
                              build_uuid=build_request.uuid),
    }
    return resp, 201, headers


@resource('/v1/repository/<path:repository>/build/<build_uuid>/status')
class RepositoryBuildStatus(RepositoryParamResource):
  """ Resource for dealing with repository build status. """
  @require_repo_read
  @nickname('getRepoBuildStatus')
  def get(self, namespace, repository, build_uuid):
    """ Return the status for the builds specified by the build uuids. """
    build = model.get_repository_build(namespace, repository, build_uuid)
    if not build:
      raise NotFound()
      
    can_write = ModifyRepositoryPermission(namespace, repository).can()
    return build_status_view(build, can_write)


@resource('/v1/repository/<path:repository>/build/<build_uuid>/logs')
class RepositoryBuildLogs(RepositoryParamResource):
  """ Resource for loading repository build logs. """
  @require_repo_write
  @nickname('getRepoBuildLogs')
  def get(self, namespace, repository, build_uuid):
    """ Return the build logs for the build specified by the build uuid. """
    response_obj = {}

    build = model.get_repository_build(namespace, repository, build_uuid)

    start = int(request.args.get('start', 0))

    count, logs = build_logs.get_log_entries(build.uuid, start)

    response_obj.update({
      'start': start,
      'total': count,
      'logs': [log for log in logs],
    })

    return response_obj


@resource('/v1/filedrop/')
@internal_only
class FileDropResource(ApiResource):
  """ Custom verb for setting up a client side file transfer. """
  schemas = {
    'FileDropRequest': {
      'id': 'FileDropRequest',
      'type': 'object',
      'description': 'Description of the file that the user wishes to upload.',
      'required': [
        'mimeType',
      ],
      'properties': {
        'mimeType': {
          'type': 'string',
          'description': 'Type of the file which is about to be uploaded',
        },
      },
    },
  }

  @nickname('getFiledropUrl')
  @validate_json_request('FileDropRequest')
  def post(self):
    """ Request a URL to which a file may be uploaded. """
    mime_type = request.get_json()['mimeType']
    (url, file_id) = user_files.prepare_for_drop(mime_type)
    return {
      'url': url,
      'file_id': str(file_id),
    }