# XXX This code is not yet ready to be run in production, and should remain disabled until such
# XXX time as this notice is removed.

import logging
import re
import time
import jwt

from flask import request, jsonify, abort
from cachetools import lru_cache

from app import app
from data import model
from auth.auth import process_auth
from auth.auth_context import get_authenticated_user
from auth.permissions import (ModifyRepositoryPermission, ReadRepositoryPermission,
                              CreateRepositoryPermission)
from endpoints.v2 import v2_bp
from util.cache import no_cache
from util.names import parse_namespace_repository


logger = logging.getLogger(__name__)


SCOPE_REGEX = re.compile(
    r'repository:([\.a-zA-Z0-9_\-]+/[\.a-zA-Z0-9_\-]+):(((push|pull|\*),)*(push|pull|\*))'
)


@lru_cache(maxsize=1)
def load_certificate_bytes(certificate_file_path):
  with open(certificate_file_path) as cert_file:
    return ''.join(cert_file.readlines()[1:-1]).rstrip('\n')


@lru_cache(maxsize=1)
def load_private_key(private_key_file_path):
  with open(private_key_file_path) as private_key_file:
    return private_key_file.read()


@v2_bp.route('/auth')
@process_auth
@no_cache
def generate_registry_jwt():
  """ This endpoint will generate a JWT conforming to the Docker registry v2 auth spec:
      https://docs.docker.com/registry/spec/auth/token/
  """
  audience_param = request.args.get('service')
  logger.debug('Request audience: %s', audience_param)

  scope_param = request.args.get('scope')
  logger.debug('Scope request: %s', scope_param)

  user = get_authenticated_user()

  access = []
  if scope_param is not None:
    match = SCOPE_REGEX.match(scope_param)
    if match is None or match.end() != len(scope_param):
      logger.debug('Match: %s', match)
      logger.debug('End: %s', match.end())
      logger.debug('len: %s', len(scope_param))
      logger.warning('Unable to decode repository and actions: %s', scope_param)
      abort(400)

    logger.debug('Match: %s', match.groups())

    namespace_and_repo = match.group(1)
    actions = match.group(2).split(',')

    namespace, reponame = parse_namespace_repository(namespace_and_repo)
    if 'pull' in actions and 'push' in actions:
      repo = model.repository.get_repository(namespace, reponame)
      if repo:
        if not ModifyRepositoryPermission(namespace, reponame):
          abort(403)
      else:
        if not CreateRepositoryPermission(namespace):
          abort(403)
        logger.debug('Creating repository: %s/%s', namespace, reponame)
        model.repository.create_repository(namespace, reponame, user)
    elif 'pull' in actions:
      if not ReadRepositoryPermission(namespace, reponame):
        abort(403)


    access.append({
      'type': 'repository',
      'name': namespace_and_repo,
      'actions': actions,
    })

  token_data = {
    'iss': 'token-issuer',
    'aud': audience_param,
    'nbf': int(time.time()),
    'exp': int(time.time() + 60),
    'sub': user.username,
    'access': access,
  }

  certificate = load_certificate_bytes(app.config['JWT_AUTH_CERTIFICATE_PATH'])

  token_headers = {
    'x5c': [certificate],
  }

  private_key = load_private_key(app.config['JWT_AUTH_PRIVATE_KEY_PATH'])

  return jsonify({'token':jwt.encode(token_data, private_key, 'RS256', headers=token_headers)})
