import logging
import stripe
import re
import requests
import urlparse
import json

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

import storage

from data import model
from data.userfiles import UserRequestFiles
from data.queue import dockerfile_build_queue
from data.plans import USER_PLANS, getPlan, isPlanActive
from app import app
from util.email import send_confirmation_email, send_recovery_email
from util.names import parse_repository_name
from util.gravatar import compute_hash
from auth.permissions import (ReadRepositoryPermission,
                              ModifyRepositoryPermission,
                              AdministerRepositoryPermission,
                              CreateRepositoryPermission,
                              AdministerOrganizationPermission,
                              OrganizationMemberPermission,
                              ViewTeamPermission)
from endpoints import registry
from endpoints.web import common_login
from util.cache import cache_control


store = storage.load()
logger = logging.getLogger(__name__)


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


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

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

@app.route('/api/plans/')
def plans_list():
  return jsonify({ 'plans': USER_PLANS })

@app.route('/api/user/', methods=['GET'])
def get_logged_in_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()
    }

  if current_user.is_anonymous():
    return jsonify({'anonymous': True})

  user = current_user.db_user()
  organizations = model.get_user_organizations(user.username)

  return jsonify({
    '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]
  })


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

  user_data = request.get_json();

  try:
    if user_data['password']:
      logger.debug('Changing password for user: %s', user.username)
      model.change_password(user, user_data['password'])
  except model.InvalidPasswordException, ex:
    error_resp = jsonify({
      'message': ex.message,
    })
    error_resp.status_code = 400
    return error_resp

  return jsonify({
    'verified': user.verified,
    'anonymous': False,
    'username': user.username,
    'email': user.email,
    'gravatar': compute_hash(user.email),
    'askForPassword': user.password_hash is None,
  })

@app.route('/api/user/', methods=['POST'])
def create_user_api():
  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'])
def signin_api():
  signin_data = request.get_json()

  username = signin_data['username']
  password = signin_data['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
def logout():
  logout_user()

  identity_changed.send(app, identity=AnonymousIdentity())

  return make_response('Success', 200)


@app.route("/api/recovery", methods=['POST'])
def send_recovery():
  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):
  users = model.get_matching_users(prefix)
  teams = []

  organization_name = request.args.get('organization', None)
  organization = None
  if organization_name:
    try:
      organization = model.get_organization(organization_name)
    except:
      pass

    if organization:    
      # TODO: ensure that the user has access to the organization
      teams = model.get_matching_teams(prefix, organization)

  def team_view(team):
    return {
      'name': team.name,
      'kind': 'team'
    }

  def user_view(user):
    # TODO: Return whether the user is outside the organization (if one is
    # specified)
    return {
      'name': user.username,
      'kind': 'user',
      'outside_org': True
    }

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


user_files = UserRequestFiles(app.config['AWS_ACCESS_KEY'],
                              app.config['AWS_SECRET_KEY'],
                              app.config['REGISTRY_S3_BUCKET'])


@app.route('/api/organization/<orgname>', methods=['GET'])
def get_organization(orgname):
  def team_view(t):
    return {
      'id': t.id,
      'name': t.name
    }

  def org_view(o, teams):
    return {
      'name': o.username,
      'gravatar': compute_hash(o.email),
      'teams': [team_view(t) for t in teams]
    }

  if current_user.is_anonymous():
    abort(404)

  user = current_user.db_user()
  org = model.get_organization(orgname)
  if not org:
    abort(404)

  teams = model.get_user_teams_within_org(user.username, org)
  return jsonify(org_view(org, teams))


@app.route('/api/organization/<orgname>/private', methods=['GET'])
def get_organization_private_allowed(orgname):  
  if current_user.is_anonymous():
    abort(404)

  user = current_user.db_user()

  try:
    organization = model.get_organization(orgname, username = user.username)
  except:
    abort(404)

  private_repos = model.get_private_repo_count(organization.username)
  if organization.stripe_id:
    cus = stripe.Customer.retrieve(organization.stripe_id)

    if cus.subscription and isPlanActive(cus.subscription):
      repos_allowed = getPlan(cus.subscription.plan.id)
      return jsonify({
        'privateAllowed': (private_repos < repos_allowed)
      })
    
  return jsonify({
    'privateAllowed': False
  })


def member_view(m):
  return {
    'username': m.username
  }

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

  if view_permission.can():
    user = current_user.db_user()
    team = None

    try:
      team = model.get_organization_team(orgname, teamname)
    except:
      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'])
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:
      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)

    return jsonify(member_view(user))

  abort(403)


@app.route('/api/organization/<orgname>/team/<teamname>/members/<membername>',
           methods=['DELETE'])
def delete_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:
      abort(404)

    # Find the user.
    user = model.get_user(membername)
    if not user:
      abort(400)
      
    # Remote the user from the team.
    model.remove_user_from_team(user, team)

    return jsonify({
      'success': True
    })

  abort(403)


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

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

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

    visibility = request.get_json()['visibility']

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

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

    return jsonify({
      'namespace': namespace_name,
      'name': repository_name
    })

  abort(403)


@app.route('/api/find/repository', methods=['GET'])
def match_repos_api():
  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_api():
  def repo_view(repo_obj):
    is_public = model.repository_is_public(repo_obj.namespace, repo_obj.name)

    return {
      'namespace': repo_obj.namespace,
      'name': repo_obj.name,
      'description': repo_obj.description,
      'is_public': is_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:
    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_api(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()
      return jsonify({
          'success': True
      })

  abort(404)


@app.route('/api/repository/<path:repository>/changevisibility',
           methods=['POST'])
@api_login_required
@parse_repository_name
def change_repo_visibility_api(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'])
      return jsonify({
          'success': True
      })

  abort(404)


@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)
    registry.delete_repository_storage(namespace, repository)
    return make_response('Deleted', 204)

  abort(404)


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


@app.route('/api/repository/<path:repository>', methods=['GET'])
@parse_repository_name
def get_repo_api(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:
    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 fount
  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
      return {
        'id': build_obj.id,
        'total_commands': None,
        'total_images': None,
        'current_command': None,
        'current_image': None,
        'image_completion_percent': None,
        'status': build_obj.phase,
        'message': None,
      }

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

  abort(403)  # Permissions denied



@app.route('/api/filedrop/', methods=['POST'])
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
  })


@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}))

    return jsonify({
      'started': True
    })

  abort(403)  # Permissions denied


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


@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>/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):
    tag_image = model.get_tag_image(namespace, repository, tag)
    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)
    org_member = OrganizationMemberPermission(namespace).can()

    return jsonify({
      'permissions': {repo_perm.team.name: role_view(repo_perm, org_member)
                      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():
    repo_perms = model.get_all_repo_users(namespace, repository)
    member = OrganizationMemberPermission(namespace).can()

    return jsonify({
      'permissions': {repo_perm.user.username: role_view(repo_perm, member)
                      for repo_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)
    org_member = OrganizationMemberPermission(namespace).can()
    return jsonify(role_view(perm, org_member))

  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(username, namespace, repository)
    org_member = OrganizationMemberPermission(namespace).can()
    return jsonify(role_view(perm, org_member))

  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))

    try:
      perm = model.set_user_repo_permission(username, namespace, repository,
                                            new_permission['role'])
    except model.DataModelException:
      logger.warning('User tried to remove themselves as admin.')
      abort(409)

    org_member = OrganizationMemberPermission(namespace).can()
    resp = jsonify(role_view(perm, org_member))
    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))

    try:
      perm = model.set_team_repo_permission(teamname, namespace, repository,
                                            new_permission['role'])
    except model.DataModelException:
      logger.warning('User tried to remove themselves as admin.')
      abort(409)

    org_member = OrganizationMemberPermission(namespace).can()
    resp = jsonify(role_view(perm, org_member))
    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:
      logger.warning('User tried to remove themselves as admin.')
      abort(409)

    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():
    try:
      model.delete_team_permission(teamname, namespace, repository)
    except model.DataModelException:
      logger.warning('User tried to remove themselves as admin.')
      abort(409)

    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'])

    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'])

    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():
    model.delete_delegate_token(namespace, repository, code)
    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/plan', methods=['PUT'])
@api_login_required
def subscribe():
  # Amount in cents
  amount = 500

  request_data = request.get_json()
  plan = request_data['plan']

  user = current_user.db_user()
  private_repos = model.get_private_repo_count(user.username)

  if not user.stripe_id:
    # Create the customer and plan simultaneously
    card = request_data['token']
    cus = stripe.Customer.create(email=user.email, plan=plan, card=card)
    user.stripe_id = cus.id
    user.save()

    resp = jsonify(subscription_view(cus.subscription, private_repos))
    resp.status_code = 201
    return resp

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

    if plan == 'free':
      cus.cancel_subscription()
      cus.save()

      response_json = {
        'plan': 'free',
        'usedPrivateRepos': private_repos,
      }

    else:
      cus.plan = plan

      # User may have been a previous customer who is resubscribing
      if 'token' in request_data:
        cus.card = request_data['token']

      cus.save()

      response_json = subscription_view(cus.subscription, private_repos)

    return jsonify(response_json)


@app.route('/api/user/plan', methods=['GET'])
@api_login_required
def get_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,
  })