import json
import logging
import urlparse

from flask import request, make_response, jsonify, session, Blueprint
from functools import wraps

from data import model
from data.queue import webhook_queue
from app import mixpanel
from auth.auth import (process_auth, get_authenticated_user,
                       get_validated_token)
from util.names import parse_repository_name
from util.email import send_confirmation_email
from auth.permissions import (ModifyRepositoryPermission, UserPermission,
                              ReadRepositoryPermission,
                              CreateRepositoryPermission)

from util.http import abort


logger = logging.getLogger(__name__)

index = Blueprint('index', __name__)

def generate_headers(role='read'):
  def decorator_method(f):
    @wraps(f)
    def wrapper(namespace, repository, *args, **kwargs):
      response = f(namespace, repository, *args, **kwargs)

      # Setting session namespace and repository
      session['namespace'] = namespace
      session['repository'] = repository

      # We run our index and registry on the same hosts for now
      registry_server = urlparse.urlparse(request.url).netloc
      response.headers['X-Docker-Endpoints'] = registry_server

      has_token_request = request.headers.get('X-Docker-Token', '')

      if has_token_request:
        repo = model.get_repository(namespace, repository)
        if repo:
          token = model.create_access_token(repo, role)
          token_str = 'signature=%s' % token.code
          response.headers['WWW-Authenticate'] = token_str
          response.headers['X-Docker-Token'] = token_str
        else:
          logger.info('Token request in non-existing repo: %s/%s' %
                      (namespace, repository))

      return response
    return wrapper
  return decorator_method


@index.route('/users', methods=['POST'])
@index.route('/users/', methods=['POST'])
def create_user():
  user_data = request.get_json()
  username = user_data['username']
  password = user_data['password']

  if username == '$token':
    try:
      model.load_token_data(password)
      return make_response('Verified', 201)
    except model.InvalidTokenException:
      abort(400, 'Invalid access token.', issue='invalid-access-token')

  elif '+' in username:
    try:
      model.verify_robot(username, password)
      return make_response('Verified', 201)
    except model.InvalidRobotException:
      abort(400, 'Invalid robot account or password.', issue='robot-login-failure')

  existing_user = model.get_user(username)
  if existing_user:
    verified = model.verify_user(username, password)
    if verified:
      return make_response('Verified', 201)
    else:
      abort(400, 'Invalid password.', issue='login-failure')

  else:
    # New user case
    new_user = model.create_user(username, 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)


@index.route('/users', methods=['GET'])
@index.route('/users/', methods=['GET'])
@process_auth
def get_user():
  if get_authenticated_user():  
    return jsonify({
      'username': get_authenticated_user().username,
      'email': get_authenticated_user().email,
    })
  abort(404)


@index.route('/users/<username>/', methods=['PUT'])
@process_auth
def update_user(username):
  permission = UserPermission(username)

  if permission.can():
    update_request = request.get_json()

    if 'password' in update_request:
      logger.debug('Updating user password.')
      model.change_password(get_authenticated_user(),
                            update_request['password'])

    if 'email' in update_request:
      logger.debug('Updating user email')
      model.update_email(get_authenticated_user(), update_request['email'])

    return jsonify({
      'username': get_authenticated_user().username,
      'email': get_authenticated_user().email,
    })

  abort(403)


@index.route('/repositories/<path:repository>', methods=['PUT'])
@process_auth
@parse_repository_name
@generate_headers(role='write')
def create_repository(namespace, repository):
  image_descriptions = json.loads(request.data)
  repo = model.get_repository(namespace, repository)

  if not repo and get_authenticated_user() is None:
    logger.debug('Attempt to create new repository without user auth.')
    abort(401,
          message='Cannot create a repository as a guest. Please login via "docker login" first.',
          issue='no-login')

  elif repo:
    permission = ModifyRepositoryPermission(namespace, repository)
    if not permission.can():
      abort(403, 
            message='You do not have permission to modify repository %(namespace)s/%(repository)s',
            issue='no-repo-write-permission',
            namespace=namespace, repository=repository)

  else:
    permission = CreateRepositoryPermission(namespace)
    if not permission.can():
      logger.info('Attempt to create a new repo with insufficient perms.')
      abort(403, 
            message='You do not have permission to create repositories in namespace "%(namespace)s"',
            issue='no-create-permission',
            namespace=namespace)

    logger.debug('Creaing repository with owner: %s' %
                 get_authenticated_user().username)
    repo = model.create_repository(namespace, repository,
                                   get_authenticated_user())

  new_repo_images = {desc['id']: desc for desc in image_descriptions}
  added_images = dict(new_repo_images)
  for existing in model.get_repository_images(namespace, repository):
    if existing.docker_image_id in new_repo_images:
      added_images.pop(existing.docker_image_id)

  for image_description in added_images.values():
    model.create_image(image_description['id'], repo)

  response = make_response('Created', 201)

  extra_params = {
    'repository': '%s/%s' % (namespace, repository),
  }

  metadata = {
    'repo': repository,
    'namespace': namespace
  }

  if get_authenticated_user():
    mixpanel.track(get_authenticated_user().username, 'push_repo',
                   extra_params)
    metadata['username'] = get_authenticated_user().username
  else:
    mixpanel.track(get_validated_token().code, 'push_repo', extra_params)
    metadata['token'] = get_validated_token().friendly_name
    metadata['token_code'] = get_validated_token().code

  model.log_action('push_repo', namespace, performer=get_authenticated_user(),
                   ip=request.remote_addr, metadata=metadata, repository=repo)

  return response


@index.route('/repositories/<path:repository>/images', methods=['PUT'])
@process_auth
@parse_repository_name
@generate_headers(role='write')
def update_images(namespace, repository):
  permission = ModifyRepositoryPermission(namespace, repository)

  if permission.can():
    repo = model.get_repository(namespace, repository)
    if not repo:
      # Make sure the repo actually exists.
      abort(404, message='Unknown repository', issue='unknown-repo')

    image_with_checksums = json.loads(request.data)

    updated_tags = {}
    for image in image_with_checksums:
      logger.debug('Setting checksum for image id: %s to %s' %
                   (image['id'], image['checksum']))
      updated_tags[image['Tag']] = image['id']
      model.set_image_checksum(image['id'], repo, image['checksum'])

    # Generate a job for each webhook that has been added to this repo
    webhooks = model.list_webhooks(namespace, repository)
    for webhook in webhooks:
      webhook_data = json.loads(webhook.parameters)
      repo_string = '%s/%s' % (namespace, repository)
      logger.debug('Creating webhook for repository \'%s\' for url \'%s\'' %
                   (repo_string, webhook_data['url']))
      webhook_data['payload'] = {
        'repository': repo_string,
        'namespace': namespace,
        'name': repository,
        'docker_url': 'quay.io/%s' % repo_string,
        'homepage': 'https://quay.io/repository/%s' % repo_string,
        'visibility': repo.visibility.name,
        'updated_tags': updated_tags,
        'pushed_image_count': len(image_with_checksums), 
      }
      webhook_queue.put(json.dumps(webhook_data))

    return make_response('Updated', 204)

  abort(403)


@index.route('/repositories/<path:repository>/images', methods=['GET'])
@process_auth
@parse_repository_name
@generate_headers(role='read')
def get_repository_images(namespace, repository):
  permission = ReadRepositoryPermission(namespace, repository)

  # TODO invalidate token?
  is_public = model.repository_is_public(namespace, repository)
  if permission.can() or is_public:
    # We can't rely on permissions to tell us if a repo exists anymore
    repo = model.get_repository(namespace, repository)
    if not repo:
      abort(404, message='Unknown repository', issue='unknown-repo')

    all_images = []
    for image in model.get_repository_images(namespace, repository):
      new_image_view = {
        'id': image.docker_image_id,
        'checksum': image.checksum,
      }
      all_images.append(new_image_view)

    resp = make_response(json.dumps(all_images), 200)
    resp.mimetype = 'application/json'

    metadata = {
      'repo': repository,
      'namespace': namespace,
    }
    if get_authenticated_user():
      metadata['username'] = get_authenticated_user().username
    elif get_validated_token():
      metadata['token'] = get_validated_token().friendly_name
      metadata['token_code'] = get_validated_token().code
    else:
      metadata['public'] = True

    pull_username = 'anonymous'
    if get_authenticated_user():
      pull_username = get_authenticated_user().username

    extra_params = {
      'repository': '%s/%s' % (namespace, repository),
    }

    mixpanel.track(pull_username, 'pull_repo', extra_params)
    model.log_action('pull_repo', namespace,
                     performer=get_authenticated_user(),
                     ip=request.remote_addr, metadata=metadata,
                     repository=repo)
    return resp

  abort(403)


@index.route('/repositories/<path:repository>/images', methods=['DELETE'])
@process_auth
@parse_repository_name
@generate_headers(role='write')
def delete_repository_images(namespace, repository):
  abort(501, 'Not Implemented', issue='not-implemented')


@index.route('/repositories/<path:repository>/auth', methods=['PUT'])
@parse_repository_name
def put_repository_auth(namespace, repository):
  abort(501, 'Not Implemented', issue='not-implemented')


@index.route('/search', methods=['GET'])
def get_search():
  abort(501, 'Not Implemented', issue='not-implemented')


@index.route('/_ping')
@index.route('/_ping')
def ping():
  response = make_response('true', 200)
  response.headers['X-Docker-Registry-Version'] = '0.6.0'
  return response