import logging
import stripe
import requests
import urlparse
import json

from flask import request, make_response, jsonify, abort, url_for
from flask.ext.login import current_user, logout_user
from flask.ext.principal import identity_changed, AnonymousIdentity
from functools import wraps
from collections import defaultdict

from data import model
from data.queue import dockerfile_build_queue
from data.plans import PLANS, get_plan
from app import app
from util.email import send_confirmation_email, send_recovery_email
from util.names import parse_repository_name, format_robot_username
from util.gravatar import compute_hash

from auth.permissions import (ReadRepositoryPermission,
                              ModifyRepositoryPermission,
                              AdministerRepositoryPermission,
                              CreateRepositoryPermission,
                              AdministerOrganizationPermission,
                              OrganizationMemberPermission,
                              ViewTeamPermission)
from endpoints.common import common_login
from util.cache import cache_control
from datetime import datetime, timedelta


store = app.config['STORAGE']
user_files = app.config['USERFILES']
logger = logging.getLogger(__name__)

route_data = None

def get_route_data():
  global route_data
  if route_data:
    return route_data

  routes = []
  for rule in app.url_map.iter_rules():
    if rule.rule.startswith('/api/'):
      endpoint_method = globals()[rule.endpoint]
      is_internal = '__internal_call' in dir(endpoint_method)
      is_org_api = '__user_call' in dir(endpoint_method)
      methods = list(rule.methods.difference(['HEAD', 'OPTIONS']))

      route = {
        'name': rule.endpoint,
        'methods': methods,
        'path': rule.rule,
        'parameters': list(rule.arguments)
      }

      if is_org_api:
        route['user_method'] = endpoint_method.__user_call

      routes.append(route)
      
  route_data = {
    'endpoints': routes
  }
  return route_data


def log_action(kind, user_or_orgname, metadata={}, repo=None):
  performer = current_user.db_user()
  model.log_action(kind, user_or_orgname, performer=performer,
                   ip=request.remote_addr, metadata=metadata, repository=repo)


def api_login_required(f):
  @wraps(f)
  def decorated_view(*args, **kwargs):
    if not current_user.is_authenticated():
      abort(401)

    if (current_user and current_user.db_user() and
        current_user.db_user().organization):
      abort(401)

    if (current_user and current_user.db_user() and
        current_user.db_user().robot):
      abort(401)

    return f(*args, **kwargs)
  return decorated_view


def internal_api_call(f):
  @wraps(f)
  def decorated_view(*args, **kwargs):
    return f(*args, **kwargs)

  decorated_view.__internal_call = True
  return decorated_view


def org_api_call(user_call_name):
  def internal_decorator(f):
    @wraps(f)
    def decorated_view(*args, **kwargs):
      return f(*args, **kwargs)
    
    decorated_view.__user_call = user_call_name
    return decorated_view

  return internal_decorator

@app.errorhandler(model.DataModelException)
def handle_dme(ex):
  return make_response(ex.message, 400)


@app.errorhandler(KeyError)
def handle_dme_key_error(ex):
  return make_response(ex.message, 400)


@app.route('/api/discovery')
def discovery():
  return jsonify(get_route_data())

  
@app.route('/api/')
@internal_api_call
def welcome():
  return make_response('welcome', 200)


@app.route('/api/plans/')
def list_plans():
  return jsonify({
    'plans': PLANS,
  })


def user_view(user):
  def org_view(o):
    admin_org = AdministerOrganizationPermission(o.username)
    return {
      'name': o.username,
      'gravatar': compute_hash(o.email),
      'is_org_admin': admin_org.can(),
      'can_create_repo': admin_org.can() or CreateRepositoryPermission(o.username).can()
    }

  organizations = model.get_user_organizations(user.username)

  return {
    'verified': user.verified,
    'anonymous': False,
    'username': user.username,
    'email': user.email,
    'gravatar': compute_hash(user.email),
    'askForPassword': user.password_hash is None,
    'organizations': [org_view(o) for o in organizations],
    'can_create_repo': True,
    'invoice_email': user.invoice_email
  }


@app.route('/api/user/', methods=['GET'])
@internal_api_call
def get_logged_in_user():
  if current_user.is_anonymous():
    return jsonify({'anonymous': True})

  user = current_user.db_user()
  if not user or user.organization:
    return jsonify({'anonymous': True})    

  return jsonify(user_view(user))


@app.route('/api/user/private', methods=['GET'])
@api_login_required
@internal_api_call
def get_user_private_count():
  user = current_user.db_user()
  private_repos = model.get_private_repo_count(user.username)
  repos_allowed = 0

  if user.stripe_id:
    cus = stripe.Customer.retrieve(user.stripe_id)
    if cus.subscription:
      plan = get_plan(cus.subscription.plan.id)
      if plan:
        repos_allowed = plan['privateRepos']
    
  return jsonify({
    'privateCount': private_repos,
    'reposAllowed': repos_allowed
  })


@app.route('/api/user/convert', methods=['POST'])
@api_login_required
@internal_api_call
def convert_user_to_organization():
  user = current_user.db_user()
  convert_data = request.get_json()

  # Ensure that the new admin user is the not user being converted.
  admin_username = convert_data['adminUser']
  if admin_username == user.username:
    error_resp = jsonify({
      'reason': 'invaliduser'
    })
    error_resp.status_code = 400
    return error_resp

  # Ensure that the sign in credentials work.
  admin_password = convert_data['adminPassword']
  if not model.verify_user(admin_username, admin_password):
    error_resp = jsonify({
      'reason': 'invaliduser'
    })
    error_resp.status_code = 400
    return error_resp

  # Subscribe the organization to the new plan.
  plan = convert_data['plan']
  subscribe(user, plan, None, True)  # Require business plans

  # Convert the user to an organization.
  model.convert_user_to_organization(user, model.get_user(admin_username))
  log_action('account_convert', user.username)

  # And finally login with the admin credentials.
  return conduct_signin(admin_username, admin_password)
  

@app.route('/api/user/', methods=['PUT'])
@api_login_required
@internal_api_call
def change_user_details():
  user = current_user.db_user()

  user_data = request.get_json()

  try:
    if 'password' in user_data:
      logger.debug('Changing password for user: %s', user.username)
      log_action('account_change_password', user.username)
      model.change_password(user, user_data['password'])

    if 'invoice_email' in user_data:
      logger.debug('Changing invoice_email for user: %s', user.username)
      model.change_invoice_email(user, user_data['invoice_email'])

  except model.InvalidPasswordException, ex:
    error_resp = jsonify({
      'message': ex.message,
    })
    error_resp.status_code = 400
    return error_resp

  return jsonify(user_view(user))


@app.route('/api/user/', methods=['POST'])
@internal_api_call
def create_new_user():
  user_data = request.get_json()

  existing_user = model.get_user(user_data['username'])
  if existing_user:
    error_resp = jsonify({
      'message': 'The username already exists'
    })
    error_resp.status_code = 400
    return error_resp

  try:
    new_user = model.create_user(user_data['username'], user_data['password'],
                                 user_data['email'])
    code = model.create_confirm_email_code(new_user)
    send_confirmation_email(new_user.username, new_user.email, code.code)
    return make_response('Created', 201)
  except model.DataModelException as ex:
    error_resp = jsonify({
      'message': ex.message,
    })
    error_resp.status_code = 400
    return error_resp


@app.route('/api/signin', methods=['POST'])
@internal_api_call
def signin_user():
  signin_data = request.get_json()

  username = signin_data['username']
  password = signin_data['password']

  return conduct_signin(username, password)


def conduct_signin(username, password):
  #TODO Allow email login
  needs_email_verification = False
  invalid_credentials = False

  verified = model.verify_user(username, password)
  if verified:
    if common_login(verified):
      return make_response('Success', 200)
    else:
      needs_email_verification = True

  else:
    invalid_credentials = True

  response = jsonify({
    'needsEmailVerification': needs_email_verification,
    'invalidCredentials': invalid_credentials,
  })
  response.status_code = 403
  return response


@app.route("/api/signout", methods=['POST'])
@api_login_required
@internal_api_call
def logout():
  logout_user()
  identity_changed.send(app, identity=AnonymousIdentity())
  return make_response('Success', 200)


@app.route("/api/recovery", methods=['POST'])
@internal_api_call
def request_recovery_email():
  email = request.get_json()['email']
  code = model.create_reset_password_email_code(email)
  send_recovery_email(email, code.code)
  return make_response('Created', 201)


@app.route('/api/users/<prefix>', methods=['GET'])
@api_login_required
def get_matching_users(prefix):
  users = model.get_matching_users(prefix)

  return jsonify({
    'users': [user.username for user in users]
  })


@app.route('/api/entities/<prefix>', methods=['GET'])
@api_login_required
def get_matching_entities(prefix):
  teams = []

  namespace_name = request.args.get('namespace', '')
  robot_namespace = None
  organization = None

  try:
    organization = model.get_organization(namespace_name)

    # namespace name was an org
    permission = OrganizationMemberPermission(namespace_name)
    if permission.can():
      robot_namespace = namespace_name

      if request.args.get('includeTeams', False):
        teams = model.get_matching_teams(prefix, organization)  

  except model.InvalidOrganizationException:
    # namespace name was a user
    if current_user.db_user().username == namespace_name:
      robot_namespace = namespace_name

  users = model.get_matching_users(prefix, robot_namespace, organization)

  def entity_team_view(team):
    result = {
      'name': team.name,
      'kind': 'team',
      'is_org_member': True
    }
    return result

  def user_view(user):
    user_json = {
      'name': user.username,
      'kind': 'user',
      'is_robot': user.is_robot,
    }

    if organization is not None:
      user_json['is_org_member'] = user.is_robot or user.is_org_member

    return user_json

  team_data = [entity_team_view(team) for team in teams]
  user_data = [user_view(user) for user in users]
  return jsonify({
    'results': team_data + user_data
  })


def team_view(orgname, team):
  view_permission = ViewTeamPermission(orgname, team.name)
  role = model.get_team_org_role(team).name
  return {
    'id': team.id,
    'name': team.name,
    'description': team.description,
    'can_view': view_permission.can(),
    'role': role
  }


@app.route('/api/organization/', methods=['POST'])
@api_login_required
@internal_api_call
def create_organization():
  org_data = request.get_json()
  existing = None

  try:
    existing = (model.get_organization(org_data['name']) or
                model.get_user(org_data['name']))
  except model.InvalidOrganizationException:
    pass

  if existing:
    error_resp = jsonify({
      'message': 'A user or organization with this name already exists'
    })
    error_resp.status_code = 400
    return error_resp

  try:
    model.create_organization(org_data['name'], org_data['email'],
                              current_user.db_user())
    return make_response('Created', 201)
  except model.DataModelException as ex:
    error_resp = jsonify({
      'message': ex.message,
    })
    error_resp.status_code = 400
    return error_resp


def org_view(o, teams):
  admin_org = AdministerOrganizationPermission(o.username)
  is_admin = admin_org.can()
  view = {
    'name': o.username,
    'email': o.email if is_admin else '',
    'gravatar': compute_hash(o.email),
    'teams': {t.name : team_view(o.username, t) for t in teams},
    'is_admin': is_admin
  }

  if is_admin:
    view['invoice_email'] = o.invoice_email

  return view


@app.route('/api/organization/<orgname>', methods=['GET'])
@api_login_required
def get_organization(orgname):
  permission = OrganizationMemberPermission(orgname)
  if permission.can():
    try:
      org = model.get_organization(orgname)
    except model.InvalidOrganizationException:
      abort(404)

    teams = model.get_teams_within_org(org)
    return jsonify(org_view(org, teams))

  abort(403)


@app.route('/api/organization/<orgname>', methods=['PUT'])
@api_login_required
@org_api_call('change_user_details')
def change_organization_details(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    try:
      org = model.get_organization(orgname)
    except model.InvalidOrganizationException:
      abort(404)
      
    org_data = request.get_json()
    if 'invoice_email' in org_data:
      logger.debug('Changing invoice_email for organization: %s', org.username)
      model.change_invoice_email(org, org_data['invoice_email'])
  
    teams = model.get_teams_within_org(org)
    return jsonify(org_view(org, teams))

  abort(403)


@app.route('/api/organization/<orgname>/members', methods=['GET'])
@api_login_required
def get_organization_members(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    try:
      org = model.get_organization(orgname)
    except model.InvalidOrganizationException:
      abort(404)
      
    # Loop to create the members dictionary. Note that the members collection
    # will return an entry for *every team* a member is on, so we will have
    # duplicate keys (which is why we pre-build the dictionary).
    members_dict = {}
    members = model.get_organization_members_with_teams(org)    
    for member in members:
      if not member.user.username in members_dict:
        members_dict[member.user.username] = {'username': member.user.username,
                                              'is_robot': member.user.robot,
                                              'teams': []}
    
      members_dict[member.user.username]['teams'].append(member.team.name)

    return jsonify({'members': members_dict})

  abort(403)


@app.route('/api/organization/<orgname>/members/<membername>', methods=['GET'])
@api_login_required
def get_organization_member(orgname, membername):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    try:
      org = model.get_organization(orgname)
    except model.InvalidOrganizationException:
      abort(404)
      
    member_dict = None
    member_teams = model.get_organization_members_with_teams(org, membername=membername)
    for member in member_teams:
      if not member_dict:
        member_dict = {'username': member.user.username,
                       'is_robot': member.user.robot,
                       'teams': []}
    
      member_dict['teams'].append(member.team.name)

    if not member_dict:
      abort(404)

    return jsonify({'member': member_dict})

  abort(403)


@app.route('/api/organization/<orgname>/private', methods=['GET'])
@api_login_required
@internal_api_call
def get_organization_private_allowed(orgname):
  permission = CreateRepositoryPermission(orgname)
  if permission.can():
    organization = model.get_organization(orgname)

    private_repos = model.get_private_repo_count(organization.username)
    if organization.stripe_id:
      cus = stripe.Customer.retrieve(organization.stripe_id)
      if cus.subscription:
        repos_allowed = 0
        plan = get_plan(cus.subscription.plan.id)
        if plan:
          repos_allowed = plan['privateRepos']
              
        return jsonify({
          'privateAllowed': (private_repos < repos_allowed)
        })
      
    return jsonify({
      'privateAllowed': False
    })

  abort(403)


def member_view(member):
  return {
    'username': member.username,
    'is_robot': member.robot,
  }


@app.route('/api/organization/<orgname>/team/<teamname>',
           methods=['PUT', 'POST'])
@api_login_required
def update_organization_team(orgname, teamname):
  edit_permission = AdministerOrganizationPermission(orgname)
  if edit_permission.can():
    team = None

    details = request.get_json()
    is_existing = False
    try:
      team = model.get_organization_team(orgname, teamname)
      is_existing = True
    except model.InvalidTeamException:
      # Create the new team.
      description = details['description'] if 'description' in details else ''
      role = details['role'] if 'role' in details else 'member'

      org = model.get_organization(orgname)
      team = model.create_team(teamname, org, role, description)
      log_action('org_create_team', orgname, {'team': teamname})

    if is_existing:
      if ('description' in details and
          team.description != details['description']):
        team.description = details['description']
        team.save()
        log_action('org_set_team_description', orgname,
                   {'team': teamname, 'description': team.description})

      if 'role' in details:
        role = model.get_team_org_role(team).name
        if role != details['role']:
          team = model.set_team_org_permission(team, details['role'],
                                               current_user.db_user().username)
          log_action('org_set_team_role', orgname,
                     {'team': teamname, 'role': details['role']})
          
    resp = jsonify(team_view(orgname, team))
    if not is_existing:
      resp.status_code = 201
    return resp

  abort(403)


@app.route('/api/organization/<orgname>/team/<teamname>',
           methods=['DELETE'])
@api_login_required
def delete_organization_team(orgname, teamname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    model.remove_team(orgname, teamname, current_user.db_user().username)
    log_action('org_delete_team', orgname, {'team': teamname})
    return make_response('Deleted', 204)

  abort(403)


@app.route('/api/organization/<orgname>/team/<teamname>/members',
           methods=['GET'])
@api_login_required
def get_organization_team_members(orgname, teamname):
  view_permission = ViewTeamPermission(orgname, teamname)
  edit_permission = AdministerOrganizationPermission(orgname)

  if view_permission.can():
    team = None
    try:
      team = model.get_organization_team(orgname, teamname)
    except model.InvalidTeamException:
      abort(404)
      
    members = model.get_organization_team_members(team.id)
    return jsonify({
      'members': { m.username : member_view(m) for m in members },
      'can_edit': edit_permission.can()
    })

  abort(403)


@app.route('/api/organization/<orgname>/team/<teamname>/members/<membername>',
           methods=['PUT', 'POST'])
@api_login_required
def update_organization_team_member(orgname, teamname, membername):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    team = None
    user = None

    # Find the team.
    try:
      team = model.get_organization_team(orgname, teamname)
    except model.InvalidTeamException:
      abort(404)

    # Find the user.
    user = model.get_user(membername)
    if not user:
      abort(400)
      
    # Add the user to the team.
    model.add_user_to_team(user, team)
    log_action('org_add_team_member', orgname,
               {'member': membername, 'team': teamname})
    return jsonify(member_view(user))

  abort(403)


@app.route('/api/organization/<orgname>/team/<teamname>/members/<membername>',
           methods=['DELETE'])
@api_login_required
def delete_organization_team_member(orgname, teamname, membername):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    # Remote the user from the team.
    invoking_user = current_user.db_user().username
    model.remove_user_from_team(orgname, teamname, membername, invoking_user)
    log_action('org_remove_team_member', orgname,
               {'member': membername, 'team': teamname})
    return make_response('Deleted', 204)

  abort(403)


@app.route('/api/repository', methods=['POST'])
@api_login_required
def create_repo():
  owner = current_user.db_user()
  req = request.get_json()
  namespace_name = req['namespace'] if 'namespace' in req else owner.username

  permission = CreateRepositoryPermission(namespace_name)
  if permission.can():
    repository_name = req['repository']
    visibility = req['visibility']

    existing = model.get_repository(namespace_name, repository_name)
    if existing:
      return make_response('Repository already exists', 400)

    visibility = req['visibility']

    repo = model.create_repository(namespace_name, repository_name, owner,
                                   visibility)
    repo.description = req['description']
    repo.save()

    log_action('create_repo', namespace_name,
               {'repo': repository_name, 'namespace': namespace_name},
               repo=repo)
    return jsonify({
      'namespace': namespace_name,
      'name': repository_name
    })

  abort(403)


@app.route('/api/find/repository', methods=['GET'])
def find_repos():
  prefix = request.args.get('query', '')

  def repo_view(repo):
    return {
      'namespace': repo.namespace,
      'name': repo.name,
      'description': repo.description
    }

  username = None
  if current_user.is_authenticated():
    username = current_user.db_user().username

  matching = model.get_matching_repositories(prefix, username)
  response = {
    'repositories': [repo_view(repo) for repo in matching]
  }

  return jsonify(response)


@app.route('/api/repository/', methods=['GET'])
def list_repos():
  def repo_view(repo_obj):
    return {
      'namespace': repo_obj.namespace,
      'name': repo_obj.name,
      'description': repo_obj.description,
      'is_public': repo_obj.visibility.name == 'public',
    }

  limit = request.args.get('limit', None)
  namespace_filter = request.args.get('namespace', None)
  include_public = request.args.get('public', 'true')
  include_private = request.args.get('private', 'true')
  sort = request.args.get('sort', 'false')

  try:
    limit = int(limit) if limit else None
  except TypeError:
    limit = None

  include_public = include_public == 'true'
  include_private = include_private == 'true'
  sort = sort == 'true'

  username = None
  if current_user.is_authenticated() and include_private:
    username = current_user.db_user().username

  repo_query = model.get_visible_repositories(username, limit=limit,
                                              include_public=include_public,
                                              sort=sort,
                                              namespace=namespace_filter)
  repos = [repo_view(repo) for repo in repo_query]
  response = {
    'repositories': repos
  }

  return jsonify(response)


@app.route('/api/repository/<path:repository>', methods=['PUT'])
@api_login_required
@parse_repository_name
def update_repo(namespace, repository):
  permission = ModifyRepositoryPermission(namespace, repository)
  if permission.can():
    repo = model.get_repository(namespace, repository)
    if repo:
      values = request.get_json()
      repo.description = values['description']
      repo.save()
      
      log_action('set_repo_description', namespace,
                 {'repo': repository, 'description': values['description']},
                 repo=repo)
      return jsonify({
          'success': True
      })

  abort(403)


@app.route('/api/repository/<path:repository>/changevisibility',
           methods=['POST'])
@api_login_required
@parse_repository_name
def change_repo_visibility(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    repo = model.get_repository(namespace, repository)
    if repo:
      values = request.get_json()
      model.set_repository_visibility(repo, values['visibility'])
      log_action('change_repo_visibility', namespace,
                 {'repo': repository, 'visibility': values['visibility']},
                 repo=repo)
      return jsonify({
          'success': True
      })

  abort(403)


@app.route('/api/repository/<path:repository>', methods=['DELETE'])
@api_login_required
@parse_repository_name
def delete_repository(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    model.purge_repository(namespace, repository)
    log_action('delete_repo', namespace,
               {'repo': repository, 'namespace': namespace})
    return make_response('Deleted', 204)

  abort(403)


def image_view(image):
  return {
    'id': image.docker_image_id,
    'created': image.created,
    'comment': image.comment,
    'ancestors': image.ancestors,
    'dbid': image.id,
    'size': image.image_size,
  }


@app.route('/api/repository/<path:repository>', methods=['GET'])
@parse_repository_name
def get_repo(namespace, repository):
  logger.debug('Get repo: %s/%s' % (namespace, repository))

  def tag_view(tag):
    image = model.get_tag_image(namespace, repository, tag.name)
    if not image:
      return {}

    return {
      'name': tag.name,
      'image': image_view(image),
    }

  organization = None
  try:
    organization = model.get_organization(namespace)
  except model.InvalidOrganizationException:
    pass

  permission = ReadRepositoryPermission(namespace, repository)
  is_public = model.repository_is_public(namespace, repository)
  if permission.can() or is_public:
    repo = model.get_repository(namespace, repository)
    if repo:
      tags = model.list_repository_tags(namespace, repository)
      tag_dict = {tag.name: tag_view(tag) for tag in tags}
      can_write = ModifyRepositoryPermission(namespace, repository).can()
      can_admin = AdministerRepositoryPermission(namespace, repository).can()
      active_builds = model.list_repository_builds(namespace, repository,
                                                   include_inactive=False)

      return jsonify({
        'namespace': namespace,
        'name': repository,
        'description': repo.description,
        'tags': tag_dict,
        'can_write': can_write,
        'can_admin': can_admin,
        'is_public': is_public,
        'is_building': len(active_builds) > 0,
        'is_organization': bool(organization)
      })

    abort(404)  # Not found
  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/build/', methods=['GET'])
@api_login_required
@parse_repository_name
def get_repo_builds(namespace, repository):
  permission = ModifyRepositoryPermission(namespace, repository)
  if permission.can():
    def build_view(build_obj):
      if build_obj.status_url:
        # Delegate the status to the build node
        node_status = requests.get(build_obj.status_url).json()
        node_status['id'] = build_obj.id
        return node_status

      # If there was no status url, do the best we can
      # The format of this block should mirror that of the buildserver.
      return {
        'id': build_obj.id,
        'total_commands': None,
        'current_command': None,
        'push_completion': 0.0,
        'status': build_obj.phase,
        'message': None,
        'image_completion': {},
      }

    builds = model.list_repository_builds(namespace, repository)
    return jsonify({
      'builds': [build_view(build) for build in builds]
    })

  abort(403)  # Permissions denied


@app.route('/api/repository/<path:repository>/build/', methods=['POST'])
@api_login_required
@parse_repository_name
def request_repo_build(namespace, repository):
  permission = ModifyRepositoryPermission(namespace, repository)
  if permission.can():
    logger.debug('User requested repository initialization.')
    dockerfile_id = request.get_json()['file_id']

    repo = model.get_repository(namespace, repository)
    token = model.create_access_token(repo, 'write')

    host = urlparse.urlparse(request.url).netloc
    tag = '%s/%s/%s' % (host, repo.namespace, repo.name)
    build_request = model.create_repository_build(repo, token, dockerfile_id,
                                                  tag)
    dockerfile_build_queue.put(json.dumps({'build_id': build_request.id}))

    log_action('build_dockerfile', namespace,
               {'repo': repository, 'namespace': namespace,
                'fileid': dockerfile_id}, repo=repo)

    resp = jsonify({
      'started': True
    })
    resp.status_code = 201
    return resp

  abort(403)  # Permissions denied


def webhook_view(webhook):
  return {
    'public_id': webhook.public_id,
    'parameters': json.loads(webhook.parameters),
  }


@app.route('/api/repository/<path:repository>/webhook/', methods=['POST'])
@api_login_required
@parse_repository_name
def create_webhook(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    repo = model.get_repository(namespace, repository)
    webhook = model.create_webhook(repo, request.get_json())
    resp = jsonify(webhook_view(webhook))
    repo_string = '%s/%s' % (namespace, repository)
    resp.headers['Location'] = url_for('get_webhook', repository=repo_string,
                                       public_id=webhook.public_id)
    log_action('add_repo_webhook', namespace, 
               {'repo': repository, 'webhook_id': webhook.public_id},
               repo=repo)
    return resp

  abort(403)  # Permissions denied


@app.route('/api/repository/<path:repository>/webhook/<public_id>',
           methods=['GET'])
@api_login_required
@parse_repository_name
def get_webhook(namespace, repository, public_id):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    webhook = model.get_webhook(namespace, repository, public_id)
    return jsonify(webhook_view(webhook))

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/webhook/', methods=['GET'])
@api_login_required
@parse_repository_name
def list_webhooks(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    webhooks = model.list_webhooks(namespace, repository)
    return jsonify({
      'webhooks': [webhook_view(webhook) for webhook in webhooks]
    })

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/webhook/<public_id>',
           methods=['DELETE'])
@api_login_required
@parse_repository_name
def delete_webhook(namespace, repository, public_id):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    model.delete_webhook(namespace, repository, public_id)
    log_action('delete_repo_webhook', namespace, 
               {'repo': repository, 'webhook_id': public_id},
               repo=model.get_repository(namespace, repository))
    return make_response('No Content', 204)

  abort(403)  # Permission denied


@app.route('/api/filedrop/', methods=['POST'])
@api_login_required
@internal_api_call
def get_filedrop_url():
  mime_type = request.get_json()['mimeType']
  (url, file_id) = user_files.prepare_for_drop(mime_type)
  return jsonify({
    'url': url,
    'file_id': file_id
  })


def role_view(repo_perm_obj):
  return {
    'role': repo_perm_obj.role.name,
  }


def wrap_role_view_user(role_json, user):
  role_json['is_robot'] = user.robot
  return role_json


def wrap_role_view_org(role_json, user, org_members):
  role_json['is_org_member'] = user.robot or user.username in org_members
  return role_json


@app.route('/api/repository/<path:repository>/image/', methods=['GET'])
@parse_repository_name
def list_repository_images(namespace, repository):
  permission = ReadRepositoryPermission(namespace, repository)
  if permission.can() or model.repository_is_public(namespace, repository):
    all_images = model.get_repository_images(namespace, repository)
    all_tags = model.list_repository_tags(namespace, repository)

    tags_by_image_id = defaultdict(list)
    for tag in all_tags:
      tags_by_image_id[tag.image.docker_image_id].append(tag.name)


    def add_tags(image_json):
      image_json['tags'] = tags_by_image_id[image_json['id']]
      return image_json


    return jsonify({
      'images': [add_tags(image_view(image)) for image in all_images]
    })

  abort(403)


@app.route('/api/repository/<path:repository>/image/<image_id>',
           methods=['GET'])
@parse_repository_name
def get_image(namespace, repository, image_id):
  permission = ReadRepositoryPermission(namespace, repository)
  if permission.can() or model.repository_is_public(namespace, repository):
    image = model.get_repo_image(namespace, repository, image_id)
    if not image:
      abort(404)

    return jsonify(image_view(image))
  abort(403)


@app.route('/api/repository/<path:repository>/image/<image_id>/changes',
           methods=['GET'])
@cache_control(max_age=60*60)  # Cache for one hour
@parse_repository_name
def get_image_changes(namespace, repository, image_id):
  permission = ReadRepositoryPermission(namespace, repository)
  if permission.can() or model.repository_is_public(namespace, repository):
    diffs_path = store.image_file_diffs_path(namespace, repository, image_id)

    try:
      response_json = store.get_content(diffs_path)
      return make_response(response_json)
    except IOError:
      abort(404)

  abort(403)


@app.route('/api/repository/<path:repository>/tag/<tag>',
           methods=['DELETE'])
@parse_repository_name
def delete_full_tag(namespace, repository, tag):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    model.delete_tag_and_images(namespace, repository, tag)

    username = current_user.db_user().username
    log_action('delete_tag', namespace,
               {'username': username, 'repo': repository, 'tag': tag},
               repo=model.get_repository(namespace, repository))

    return make_response('Deleted', 204)

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/tag/<tag>/images',
           methods=['GET'])
@parse_repository_name
def list_tag_images(namespace, repository, tag):
  permission = ReadRepositoryPermission(namespace, repository)
  if permission.can() or model.repository_is_public(namespace, repository):
    try:
      tag_image = model.get_tag_image(namespace, repository, tag)
    except model.DataModelException:
      abort(404)

    parent_images = model.get_parent_images(tag_image)

    parents = list(parent_images)
    parents.reverse()
    all_images = [tag_image] + parents

    return jsonify({
      'images': [image_view(image) for image in all_images]
    })

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/team/',
           methods=['GET'])
@api_login_required
@parse_repository_name
def list_repo_team_permissions(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    repo_perms = model.get_all_repo_teams(namespace, repository)

    return jsonify({
      'permissions': {repo_perm.team.name: role_view(repo_perm)
                      for repo_perm in repo_perms}
    })

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/user/',
           methods=['GET'])
@api_login_required
@parse_repository_name
def list_repo_user_permissions(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    # Lookup the organization (if any).
    org = None
    try:
      org = model.get_organization(namespace)  # Will raise an error if not org
    except model.InvalidOrganizationException:
      # This repository isn't under an org
      pass

    # Determine how to wrap the role(s).
    def wrapped_role_view(repo_perm):
      return wrap_role_view_user(role_view(repo_perm), repo_perm.user)

    role_view_func = wrapped_role_view

    if org:
      org_members = model.get_organization_member_set(namespace)
      current_func = role_view_func

      def wrapped_role_org_view(repo_perm):
        return wrap_role_view_org(current_func(repo_perm), repo_perm.user,
                                  org_members)

      role_view_func = wrapped_role_org_view
    
    # Load and return the permissions.
    repo_perms = model.get_all_repo_users(namespace, repository)
    return jsonify({
      'permissions': {perm.user.username: role_view_func(perm)
                      for perm in repo_perms}
    })

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/user/<username>',
           methods=['GET'])
@api_login_required
@parse_repository_name
def get_user_permissions(namespace, repository, username):
  logger.debug('Get repo: %s/%s permissions for user %s' %
               (namespace, repository, username))
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    perm = model.get_user_reponame_permission(username, namespace, repository)
    perm_view = wrap_role_view_user(role_view(perm), perm.user)

    try:
      model.get_organization(namespace)
      org_members = model.get_organization_member_set(namespace)
      perm_view = wrap_role_view_org(perm_view, perm.user, org_members)
    except model.InvalidOrganizationException:
      # This repository is not part of an organization
      pass

    return jsonify(perm_view)

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
           methods=['GET'])
@api_login_required
@parse_repository_name
def get_team_permissions(namespace, repository, teamname):
  logger.debug('Get repo: %s/%s permissions for team %s' %
               (namespace, repository, teamname))
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    perm = model.get_team_reponame_permission(teamname, namespace, repository)
    return jsonify(role_view(perm))

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/user/<username>',
           methods=['PUT', 'POST'])
@api_login_required
@parse_repository_name
def change_user_permissions(namespace, repository, username):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    new_permission = request.get_json()

    logger.debug('Setting permission to: %s for user %s' %
                 (new_permission['role'], username))

    perm = model.set_user_repo_permission(username, namespace, repository,
                                          new_permission['role'])
    perm_view = wrap_role_view_user(role_view(perm), perm.user)

    try:
      model.get_organization(namespace)
      org_members = model.get_organization_member_set(namespace)
      perm_view = wrap_role_view_org(perm_view, perm.user, org_members)
    except model.InvalidOrganizationException:
      # This repository is not part of an organization
      pass
    except model.DataModelException as ex:
      error_resp = jsonify({
          'message': ex.message,
          })
      error_resp.status_code = 400
      return error_resp
      
    log_action('change_repo_permission', namespace,
               {'username': username, 'repo': repository,
                'role': new_permission['role']},
               repo=model.get_repository(namespace, repository))

    resp = jsonify(perm_view)
    if request.method == 'POST':
      resp.status_code = 201
    return resp

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
           methods=['PUT', 'POST'])
@api_login_required
@parse_repository_name
def change_team_permissions(namespace, repository, teamname):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    new_permission = request.get_json()

    logger.debug('Setting permission to: %s for team %s' %
                 (new_permission['role'], teamname))

    perm = model.set_team_repo_permission(teamname, namespace, repository,
                                          new_permission['role'])

    log_action('change_repo_permission', namespace,
               {'team': teamname, 'repo': repository,
                'role': new_permission['role']},
               repo=model.get_repository(namespace, repository))

    resp = jsonify(role_view(perm))
    if request.method == 'POST':
      resp.status_code = 201
    return resp

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/user/<username>',
           methods=['DELETE'])
@api_login_required
@parse_repository_name
def delete_user_permissions(namespace, repository, username):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    try:
      model.delete_user_permission(username, namespace, repository)
    except model.DataModelException as ex:
      error_resp = jsonify({
          'message': ex.message,
          })
      error_resp.status_code = 400
      return error_resp

    log_action('delete_repo_permission', namespace,
               {'username': username, 'repo': repository},
               repo=model.get_repository(namespace, repository))
    
    return make_response('Deleted', 204)

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/permissions/team/<teamname>',
           methods=['DELETE'])
@api_login_required
@parse_repository_name
def delete_team_permissions(namespace, repository, teamname):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    model.delete_team_permission(teamname, namespace, repository)

    log_action('delete_repo_permission', namespace,
               {'team': teamname, 'repo': repository},
               repo=model.get_repository(namespace, repository))

    return make_response('Deleted', 204)

  abort(403)  # Permission denied


def token_view(token_obj):
  return {
    'friendlyName': token_obj.friendly_name,
    'code': token_obj.code,
    'role': token_obj.role.name,
  }


@app.route('/api/repository/<path:repository>/tokens/', methods=['GET'])
@api_login_required
@parse_repository_name
def list_repo_tokens(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    tokens = model.get_repository_delegate_tokens(namespace, repository)

    return jsonify({
      'tokens': {token.code: token_view(token) for token in tokens}
    })

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/tokens/<code>', methods=['GET'])
@api_login_required
@parse_repository_name
def get_tokens(namespace, repository, code):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    perm = model.get_repo_delegate_token(namespace, repository, code)
    return jsonify(token_view(perm))

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/tokens/', methods=['POST'])
@api_login_required
@parse_repository_name
def create_token(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    token_params = request.get_json()

    token = model.create_delegate_token(namespace, repository,
                                        token_params['friendlyName'])

    log_action('add_repo_accesstoken', namespace,
               {'repo': repository, 'token': token_params['friendlyName']},
               repo = model.get_repository(namespace, repository))

    resp = jsonify(token_view(token))
    resp.status_code = 201
    return resp

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/tokens/<code>', methods=['PUT'])
@api_login_required
@parse_repository_name
def change_token(namespace, repository, code):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    new_permission = request.get_json()

    logger.debug('Setting permission to: %s for code %s' %
                 (new_permission['role'], code))

    token = model.set_repo_delegate_token_role(namespace, repository, code,
                                               new_permission['role'])

    log_action('change_repo_permission', namespace,
               {'repo': repository, 'token': token.friendly_name, 'code': code,
                'role': new_permission['role']},
               repo = model.get_repository(namespace, repository))

    resp = jsonify(token_view(token))
    return resp

  abort(403)  # Permission denied


@app.route('/api/repository/<path:repository>/tokens/<code>',
           methods=['DELETE'])
@api_login_required
@parse_repository_name
def delete_token(namespace, repository, code):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    token = model.delete_delegate_token(namespace, repository, code)

    log_action('delete_repo_accesstoken', namespace,
               {'repo': repository, 'token': token.friendly_name,
                'code': code},
               repo = model.get_repository(namespace, repository))

    return make_response('Deleted', 204)

  abort(403)  # Permission denied


def subscription_view(stripe_subscription, used_repos):
  return {
    'currentPeriodStart': stripe_subscription.current_period_start,
    'currentPeriodEnd': stripe_subscription.current_period_end,
    'plan': stripe_subscription.plan.id,
    'usedPrivateRepos': used_repos,
  }


@app.route('/api/user/card', methods=['GET'])
@api_login_required
@internal_api_call
def get_user_card():
  user = current_user.db_user()
  return get_card(user)


@app.route('/api/organization/<orgname>/card', methods=['GET'])
@api_login_required
@internal_api_call
@org_api_call('get_user_card')
def get_org_card(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    organization = model.get_organization(orgname)
    return get_card(organization)

  abort(403)


@app.route('/api/user/card', methods=['POST'])
@api_login_required
@internal_api_call
def set_user_card():
  user = current_user.db_user()
  token = request.get_json()['token']
  response = set_card(user, token)
  log_action('account_change_cc', user.username)
  return response


@app.route('/api/organization/<orgname>/card', methods=['POST'])
@api_login_required
@org_api_call('set_user_card')
def set_org_card(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    organization = model.get_organization(orgname)
    token = request.get_json()['token']
    response = set_card(organization, token)
    log_action('account_change_cc', orgname)
    return response

  abort(403)


def set_card(user, token):
  if user.stripe_id:
    cus = stripe.Customer.retrieve(user.stripe_id)
    if cus:
      try:
        cus.card = token
        cus.save()
      except stripe.CardError as e:
        return carderror_response(e)

  return get_card(user)


def get_card(user):
  card_info = {
    'is_valid': False
  }

  if user.stripe_id:
    cus = stripe.Customer.retrieve(user.stripe_id)
    if cus and cus.default_card:
      # Find the default card.
      default_card = None
      for card in cus.cards.data:
        if card.id == cus.default_card:
          default_card = card
          break

      if default_card:
        card_info = {
          'owner': default_card.name,
          'type': default_card.type,
          'last4': default_card.last4
        }

  return jsonify({'card': card_info})

@app.route('/api/user/plan', methods=['PUT'])
@api_login_required
@internal_api_call
def update_user_subscription():
  request_data = request.get_json()
  plan = request_data['plan']
  token = request_data['token'] if 'token' in request_data else None
  user = current_user.db_user()
  return subscribe(user, plan, token, False)  # Business features not required


def carderror_response(e):
  resp = jsonify({
    'carderror': e.message,
  })
  resp.status_code = 402
  return resp


def subscribe(user, plan, token, require_business_plan):
  plan_found = None
  for plan_obj in PLANS:
    if plan_obj['stripeId'] == plan:
      plan_found = plan_obj

  if not plan_found or plan_found['deprecated']:
    logger.warning('Plan not found or deprecated: %s', plan)
    abort(404)

  if (require_business_plan and not plan_found['bus_features'] and not
                                  plan_found['price'] == 0):
    logger.warning('Business attempting to subscribe to personal plan: %s',
                   user.username)
    abort(400)

  private_repos = model.get_private_repo_count(user.username)

  # This is the default response
  response_json = {
    'plan': plan,
    'usedPrivateRepos': private_repos,
  }
  status_code = 200

  if not user.stripe_id:
    # Check if a non-paying user is trying to subscribe to a free plan
    if not plan_found['price'] == 0:
      # They want a real paying plan, create the customer and plan
      # simultaneously
      card = token
      
      try:
        cus = stripe.Customer.create(email=user.email, plan=plan, card=card)
        user.stripe_id = cus.id
        user.save()
        log_action('account_change_plan', user.username, {'plan': plan})
      except stripe.CardError as e:
        return carderror_response(e)

      response_json = subscription_view(cus.subscription, private_repos)
      status_code = 201

  else:
    # Change the plan
    cus = stripe.Customer.retrieve(user.stripe_id)

    if plan_found['price'] == 0:
      if cus.subscription is not None:
        # We only have to cancel the subscription if they actually have one
        cus.cancel_subscription()
        cus.save()
        log_action('account_change_plan', user.username, {'plan': plan})

    else:
      # User may have been a previous customer who is resubscribing
      if token:
        cus.card = token

      cus.plan = plan

      try:
        cus.save()
      except stripe.CardError as e:
        return carderror_response(e)
          
      response_json = subscription_view(cus.subscription, private_repos)
      log_action('account_change_plan', user.username, {'plan': plan})

  resp = jsonify(response_json)
  resp.status_code = status_code
  return resp


@app.route('/api/user/invoices', methods=['GET'])
@api_login_required
def list_user_invoices():
  user = current_user.db_user()
  if not user.stripe_id:
    abort(404)
      
  return get_invoices(user.stripe_id)


@app.route('/api/organization/<orgname>/invoices', methods=['GET'])
@api_login_required
@org_api_call('list_user_invoices')
def list_org_invoices(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    organization = model.get_organization(orgname)
    if not organization.stripe_id:
      abort(404)
      
    return get_invoices(organization.stripe_id)

  abort(403)


def get_invoices(customer_id):
  def invoice_view(i):
    return {
      'id': i.id,
      'date': i.date,
      'period_start': i.period_start,
      'period_end': i.period_end,
      'paid': i.paid,
      'amount_due': i.amount_due,
      'next_payment_attempt': i.next_payment_attempt,
      'attempted': i.attempted,
      'closed': i.closed,
      'total': i.total,
      'plan': i.lines.data[0].plan.id if i.lines.data[0].plan else None
    }
    
  invoices = stripe.Invoice.all(customer=customer_id, count=12)
  return jsonify({
    'invoices': [invoice_view(i) for i in invoices.data]
  })


@app.route('/api/organization/<orgname>/plan', methods=['PUT'])
@api_login_required
@internal_api_call
@org_api_call('update_user_subscription')
def update_org_subscription(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    request_data = request.get_json()
    plan = request_data['plan']
    token = request_data['token'] if 'token' in request_data else None
    organization = model.get_organization(orgname)
    return subscribe(organization, plan, token, True)  # Business plan required

  abort(403)
  

@app.route('/api/user/plan', methods=['GET'])
@api_login_required
@internal_api_call
def get_user_subscription():
  user = current_user.db_user()
  private_repos = model.get_private_repo_count(user.username)

  if user.stripe_id:
    cus = stripe.Customer.retrieve(user.stripe_id)

    if cus.subscription:                                
      return jsonify(subscription_view(cus.subscription, private_repos))

  return jsonify({
    'plan': 'free',
    'usedPrivateRepos': private_repos,
  })


@app.route('/api/organization/<orgname>/plan', methods=['GET'])
@api_login_required
@internal_api_call
@org_api_call('get_user_subscription')
def get_org_subscription(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    private_repos = model.get_private_repo_count(orgname)
    organization = model.get_organization(orgname)
    if organization.stripe_id:
      cus = stripe.Customer.retrieve(organization.stripe_id)

      if cus.subscription:                                
        return jsonify(subscription_view(cus.subscription, private_repos))

    return jsonify({
      'plan': 'free',
      'usedPrivateRepos': private_repos,
    })

  abort(403)


def robot_view(name, token):
  return {
    'name': name,
    'token': token,
  }


@app.route('/api/user/robots', methods=['GET'])
@api_login_required
def get_user_robots():
  user = current_user.db_user()
  robots = model.list_entity_robots(user.username)
  return jsonify({
    'robots': [robot_view(name, password) for name, password in robots]
  })


@app.route('/api/organization/<orgname>/robots', methods=['GET'])
@api_login_required
@org_api_call('get_user_robots')
def get_org_robots(orgname):
  permission = OrganizationMemberPermission(orgname)
  if permission.can():
    robots = model.list_entity_robots(orgname)
    return jsonify({
      'robots': [robot_view(name, password) for name, password in robots]
    })

  abort(403)


@app.route('/api/user/robots/<robot_shortname>', methods=['PUT'])
@api_login_required
def create_user_robot(robot_shortname):
  parent = current_user.db_user()
  robot, password = model.create_robot(robot_shortname, parent)
  resp = jsonify(robot_view(robot.username, password))
  log_action('create_robot', parent.username, {'robot': robot_shortname})
  resp.status_code = 201
  return resp


@app.route('/api/organization/<orgname>/robots/<robot_shortname>',
           methods=['PUT'])
@api_login_required
@org_api_call('create_user_robot')
def create_org_robot(orgname, robot_shortname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    parent = model.get_organization(orgname)
    robot, password = model.create_robot(robot_shortname, parent)
    resp = jsonify(robot_view(robot.username, password))
    log_action('create_robot', orgname,  {'robot': robot_shortname})
    resp.status_code = 201
    return resp

  abort(403)


@app.route('/api/user/robots/<robot_shortname>', methods=['DELETE'])
@api_login_required
def delete_user_robot(robot_shortname):
  parent = current_user.db_user()
  model.delete_robot(format_robot_username(parent.username, robot_shortname))
  log_action('delete_robot', parent.username, {'robot': robot_shortname})
  return make_response('No Content', 204)


@app.route('/api/organization/<orgname>/robots/<robot_shortname>',
           methods=['DELETE'])
@api_login_required
@org_api_call('delete_user_robot')
def delete_org_robot(orgname, robot_shortname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    model.delete_robot(format_robot_username(orgname, robot_shortname))
    log_action('delete_robot', orgname, {'robot': robot_shortname})
    return make_response('No Content', 204)

  abort(403)


def log_view(log):
  view = {
    'kind': log.kind.name,
    'metadata': json.loads(log.metadata_json),
    'ip': log.ip,      
    'datetime': log.datetime,
  }

  if log.performer:
    view['performer'] = {
      'username': log.performer.username,
      'is_robot': log.performer.robot,
    }

  return view



@app.route('/api/repository/<path:repository>/logs', methods=['GET'])
@api_login_required
@parse_repository_name
def list_repo_logs(namespace, repository):
  permission = AdministerRepositoryPermission(namespace, repository)
  if permission.can():
    repo = model.get_repository(namespace, repository)
    if not repo:
      abort(404)

    start_time = request.args.get('starttime', None)
    end_time = request.args.get('endtime', None)
    return get_logs(namespace, start_time, end_time, repository=repo)

  abort(403)


@app.route('/api/organization/<orgname>/logs', methods=['GET'])
@api_login_required
@org_api_call('list_user_logs')
def list_org_logs(orgname):
  permission = AdministerOrganizationPermission(orgname)
  if permission.can():
    performer_name = request.args.get('performer', None)
    start_time = request.args.get('starttime', None)
    end_time = request.args.get('endtime', None)

    return get_logs(orgname, start_time, end_time,
                    performer_name=performer_name)

  abort(403)


@app.route('/api/user/logs', methods=['GET'])
@api_login_required
def list_user_logs():
  performer_name = request.args.get('performer', None)
  start_time = request.args.get('starttime', None)
  end_time = request.args.get('endtime', None)

  return get_logs(current_user.db_user().username, start_time, end_time,
                  performer_name=performer_name)


def get_logs(namespace, start_time, end_time, performer_name=None,
             repository=None):
  performer = None
  if performer_name:
    performer = model.get_user(performer_name)

  if start_time:
    try:
      start_time = datetime.strptime(start_time + ' UTC', '%m/%d/%Y %Z')
    except ValueError:
      start_time = None

  if not start_time:  
    start_time = datetime.today() - timedelta(7) # One week

  if end_time:
    try:
      end_time = datetime.strptime(end_time + ' UTC', '%m/%d/%Y %Z')
      end_time = end_time + timedelta(days=1)
    except ValueError:
      end_time = None
      
  if not end_time:  
    end_time = datetime.today()

  logs = model.list_logs(namespace, start_time, end_time, performer=performer,
                         repository=repository)
  return jsonify({
    'start_time': start_time,
    'end_time': end_time,
    'logs': [log_view(log) for log in logs]
  })