import jwt
import time

from .exceptions import ExpiredToken, InvalidTokenSignature
from .type_validation_functions import anything


class JWTManager:

  def __init__(self, config):
    self.config = config

  def __call__(self, profile_format=anything):
    def decorator(fn):
      if not getattr(fn, '_is_route', False):
        raise Exception("Can only use a 'jwt' decorator over a 'route' decorator")
      fn._profile_format = profile_format
      return fn
    return decorator

  def encode(self, auth_profile):
    """
    Uses the configured parameters to construct a JWT containing the input auth_profile for use with this
    OPService instance. Returns a serializable JWT that can be passed in through the 'Authorization' header on
    subsequent calls using the format "Bearer JWT_TOKEN"

    Note: this method belongs to the OPService class because it is an integral component to how service calls are
    handled and arguments are passed through to functions decorated with 'route'

    Parameters:
      auth_profile: the dictionary to encode as the JWT - note that some claim names are reserved and have special
        meanings ('iss', 'sub', 'aud', 'exp', 'nbf', 'iat', 'jti'). By default, 'iat' and 'exp' are set automatically,
        but precedence is given to any values you pass in.

    Examples:
    >>> plugin = JWTManager({ 'secret': 'ABCDEFGH', 'exp_s': 3600 })
    >>> plugin.encode({ "user_id": "123", "roles": [ "admin" ] })
    >>> # Override the 'exp' property (this JWT will never be valid)
    >>> plugin.encode({ "user_id": "123", "roles": [ "admin" ], "exp": 0 })
    """
    now = int(time.time())
    to_encode = { 'iat': now, 'exp': now + self.config['exp_s'] }
    to_encode.update(auth_profile)
    token = jwt.encode(to_encode, self.config['secret'])
    return str(token, 'UTF-8')

  # See here for more about available options: https://pyjwt.readthedocs.io/en/latest/usage.html
  def decode(self, token, options={ 'require': [ 'iat', 'exp' ] }):
    try:
      return jwt.decode(token, self.config['secret'], options=options)
    except jwt.ExpiredSignatureError as e:
      raise ExpiredToken() from e
    except Exception as e:
      raise InvalidTokenSignature() from e
