import logging
import json
import os

from datetime import datetime, timedelta
from data.users.federated import FederatedUsers, VerifiedCredentials
from util.security import strictjwt


logger = logging.getLogger(__name__)


class ExternalJWTAuthN(FederatedUsers):
  """ Delegates authentication to a REST endpoint that returns JWTs. """
  PUBLIC_KEY_FILENAME = 'jwt-authn.cert'

  def __init__(self, verify_url, issuer, override_config_dir, http_client, max_fresh_s,
               public_key_path=None):
    super(ExternalJWTAuthN, self).__init__('jwtauthn')
    self.verify_url = verify_url
    self.issuer = issuer
    self.client = http_client
    self.max_fresh_s = max_fresh_s

    default_key_path = os.path.join(override_config_dir, ExternalJWTAuthN.PUBLIC_KEY_FILENAME)
    public_key_path = public_key_path or default_key_path
    if not os.path.exists(public_key_path):
      error_message = ('JWT Authentication public key file "%s" not found in directory %s' %
                       (ExternalJWTAuthN.PUBLIC_KEY_FILENAME, override_config_dir))

      raise Exception(error_message)

    with open(public_key_path) as public_key_file:
      self.public_key = public_key_file.read()

  def verify_credentials(self, username_or_email, password):
    result = self.client.get(self.verify_url, timeout=2, auth=(username_or_email, password))

    if result.status_code != 200:
      return (None, result.text or 'Invalid username or password')

    try:
      result_data = json.loads(result.text)
    except ValueError:
      raise Exception('Returned JWT Authentication body does not contain JSON')

    # Load the JWT returned.
    encoded = result_data.get('token', '')
    try:
      payload = strictjwt.decode(encoded, self.public_key, algorithms=['RS256'],
                                 audience='quay.io/jwtauthn', issuer=self.issuer)
    except strictjwt.InvalidTokenError:
      logger.exception('Exception when decoding returned JWT')
      return (None, 'Invalid username or password')

    if not 'sub' in payload:
      raise Exception('Missing username field in JWT')

    if not 'email' in payload:
      raise Exception('Missing email field in JWT')

    if not 'exp' in payload:
      raise Exception('Missing exp field in JWT')

    # Verify that the expiration is no more than self.max_fresh_s seconds in the future.
    expiration = datetime.utcfromtimestamp(payload['exp'])
    if expiration > datetime.utcnow() + timedelta(seconds=self.max_fresh_s):
      logger.debug('Payload expiration is outside of the %s second window: %s', self.max_fresh_s,
                   payload['exp'])
      return (None, 'Invalid username or password')

    # Parse out the username and email.
    return (VerifiedCredentials(username=payload['sub'], email=payload['email']), None)