import json
import urllib
import json
import logging

from flask import request, make_response, jsonify, abort
from functools import wraps

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


logger = logging.getLogger(__name__)


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

    response.headers['X-Docker-Endpoints'] = app.config['REGISTRY_SERVER']

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

    if has_token_request and get_authenticated_user():
      repo = model.get_repository(namespace, repository)
      token = model.create_access_token(get_authenticated_user(), repo)
      token_str = 'signature=%s,repository="%s/%s"' % (token.code, namespace,
                                                     repository)
      response.headers['WWW-Authenticate'] = token_str
      response.headers['X-Docker-Token'] = token_str

    return response
  return wrapper


@app.route('/v1/users', methods=['POST'])
@app.route('/v1/users/', methods=['POST'])
def create_user():
  user_data = request.get_json()
  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)


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


@app.route('/v1/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)


@app.route('/v1/repositories/<path:repository>', methods=['PUT'])
@process_auth
@parse_repository_name
@generate_headers
def create_repository(namespace, repository):
  image_descriptions = json.loads(request.data)

  repo = model.get_repository(namespace, repository)

  if repo:
    permission = ModifyRepositoryPermission(namespace, repository)
    if not permission.can():
      abort(403)

  else:
    if get_authenticated_user().username != namespace:
      abort(403)

    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.image_id in new_repo_images:
      added_images.pop(existing.image_id)
    else:
      existing.repositoryimage.delete()

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

  response = make_response('Created', 201)
  return response


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

  if permission.can():
    repository = model.get_repository(namespace, repository)
    image_with_checksums = json.loads(request.data)

    for image in image_with_checksums:
      logger.debug('Setting checksum for image id: %s to %s' %
             (image['id'], image['checksum']))
      model.set_image_checksum(image['id'], repository, image['checksum'])

    return make_response('Updated', 204)

  abort(403)


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

  # TODO invalidate token?

  if permission.can() or model.repository_is_public(namespace, repository):
    all_images = []
    for image in model.get_repository_images(namespace, repository):
      new_image_view = {
        'id': image.image_id,
        'checksum': image.checksum,
      }
      all_images.append(new_image_view)

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

    return resp

  abort(403)


@app.route('/v1/repositories/<path:repository>/images', methods=['DELETE'])
@process_auth
@parse_repository_name
@generate_headers
def delete_repository_images(namespace, repository):
  pass


@app.route('/v1/repositories/<path:repository>/auth', methods=['PUT'])
@parse_repository_name
def put_repository_auth(namespace, repository):
  pass


@app.route('/v1/search', methods=['GET'])
def get_search():
  pass


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