from abc import ABCMeta, abstractmethod
from six import add_metaclass
from enum import Enum

from data import model

from auth.credential_consts import (ACCESS_TOKEN_USERNAME, OAUTH_TOKEN_USERNAME,
                                    APP_SPECIFIC_TOKEN_USERNAME)

class ContextEntityKind(Enum):
  """ Defines the various kinds of entities in an auth context. Note that the string values of
      these fields *must* match the names of the fields in the ValidatedAuthContext class, as
      we fill them in directly based on the string names here.
  """
  anonymous = 'anonymous'
  user = 'user'
  robot = 'robot'
  token = 'token'
  oauthtoken = 'oauthtoken'
  appspecifictoken = 'appspecifictoken'
  signed_data = 'signed_data'


@add_metaclass(ABCMeta)
class ContextEntityHandler(object):
  """
  Interface that represents handling specific kinds of entities under an auth context.
  """

  @abstractmethod
  def credential_username(self, entity_reference):
    """ Returns the username to create credentials for this entity, if any. """
    pass

  @abstractmethod
  def get_serialized_entity_reference(self, entity_reference):
    """ Returns the entity reference for this kind of auth context, serialized into a form that can
        be placed into a JSON object and put into a JWT. This is typically a DB UUID or another
        unique identifier for the object in the DB.
    """
    pass

  @abstractmethod
  def deserialize_entity_reference(self, serialized_entity_reference):
    """ Returns the deserialized reference to the entity in the database, or None if none. """
    pass

  @abstractmethod
  def description(self, entity_reference):
    """ Returns a human-readable and *public* description of the current entity. """
    pass

  @abstractmethod
  def analytics_id_and_public_metadata(self, entity_reference):
    """ Returns the analyitics ID and a dict of public metadata for the current entity. """
    pass


class AnonymousEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return None

  def get_serialized_entity_reference(self, entity_reference):
    return None

  def deserialize_entity_reference(self, serialized_entity_reference):
    return None

  def description(self, entity_reference):
    return "anonymous"

  def analytics_id_and_public_metadata(self, entity_reference):
    return "anonymous", {}


class UserEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return entity_reference.username

  def get_serialized_entity_reference(self, entity_reference):
    return entity_reference.uuid

  def deserialize_entity_reference(self, serialized_entity_reference):
    return model.user.get_user_by_uuid(serialized_entity_reference)

  def description(self, entity_reference):
    return "user %s" % entity_reference.username

  def analytics_id_and_public_metadata(self, entity_reference):
    return entity_reference.username, {
      'username': entity_reference.username,
    }


class RobotEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return entity_reference.username

  def get_serialized_entity_reference(self, entity_reference):
    return entity_reference.username

  def deserialize_entity_reference(self, serialized_entity_reference):
    return model.user.lookup_robot(serialized_entity_reference)

  def description(self, entity_reference):
    return "robot %s" % entity_reference.username

  def analytics_id_and_public_metadata(self, entity_reference):
    return entity_reference.username, {
      'username': entity_reference.username,
      'is_robot': True,
    }


class TokenEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return ACCESS_TOKEN_USERNAME

  def get_serialized_entity_reference(self, entity_reference):
    return entity_reference.code

  def deserialize_entity_reference(self, serialized_entity_reference):
    return model.token.load_token_data(serialized_entity_reference)

  def description(self, entity_reference):
    return "token %s" % entity_reference.friendly_name

  def analytics_id_and_public_metadata(self, entity_reference):
    return 'token:%s' % entity_reference.id, {
      'token': entity_reference.friendly_name,
    }


class OAuthTokenEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return OAUTH_TOKEN_USERNAME

  def get_serialized_entity_reference(self, entity_reference):
    return entity_reference.uuid

  def deserialize_entity_reference(self, serialized_entity_reference):
    return model.oauth.lookup_access_token_by_uuid(serialized_entity_reference)

  def description(self, entity_reference):
    return "oauthtoken for user %s" % entity_reference.authorized_user.username

  def analytics_id_and_public_metadata(self, entity_reference):
    return 'oauthtoken:%s' % entity_reference.id, {
      'oauth_token_id': entity_reference.id,
      'oauth_token_application_id': entity_reference.application.client_id,
      'oauth_token_application': entity_reference.application.name,
      'username': entity_reference.authorized_user.username,
    }


class AppSpecificTokenEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return APP_SPECIFIC_TOKEN_USERNAME

  def get_serialized_entity_reference(self, entity_reference):
    return entity_reference.uuid

  def deserialize_entity_reference(self, serialized_entity_reference):
    return model.appspecifictoken.get_token_by_uuid(serialized_entity_reference)

  def description(self, entity_reference):
    tpl = (entity_reference.title, entity_reference.user.username)
    return "app specific token %s for user %s" % tpl

  def analytics_id_and_public_metadata(self, entity_reference):
    return 'appspecifictoken:%s' % entity_reference.id, {
      'app_specific_token': entity_reference.uuid,
      'app_specific_token_title': entity_reference.title,
      'username': entity_reference.user.username,
    }


class SignedDataEntityHandler(ContextEntityHandler):
  def credential_username(self, entity_reference):
    return None

  def get_serialized_entity_reference(self, entity_reference):
    raise NotImplementedError

  def deserialize_entity_reference(self, serialized_entity_reference):
    raise NotImplementedError

  def description(self, entity_reference):
    return "signed"

  def analytics_id_and_public_metadata(self, entity_reference):
    return 'signed', {'signed': entity_reference}


CONTEXT_ENTITY_HANDLERS = {
  ContextEntityKind.anonymous: AnonymousEntityHandler,
  ContextEntityKind.user: UserEntityHandler,
  ContextEntityKind.robot: RobotEntityHandler,
  ContextEntityKind.token: TokenEntityHandler,
  ContextEntityKind.oauthtoken: OAuthTokenEntityHandler,
  ContextEntityKind.appspecifictoken: AppSpecificTokenEntityHandler,
  ContextEntityKind.signed_data: SignedDataEntityHandler,
}