import logging
import os

from keystoneclient.v2_0 import client as kclient
from keystoneclient.v3 import client as kv3client
from keystoneclient.exceptions import AuthorizationFailure as KeystoneAuthorizationFailure
from keystoneclient.exceptions import Unauthorized as KeystoneUnauthorized
from data.users.federated import FederatedUsers, UserInformation
from util.itertoolrecipes import take

logger = logging.getLogger(__name__)

DEFAULT_TIMEOUT = 10 # seconds

def get_keystone_users(auth_version, auth_url, admin_username, admin_password, admin_tenant,
                       timeout=None, requires_email=True):
  if auth_version == 3:
    return KeystoneV3Users(auth_url, admin_username, admin_password, admin_tenant, timeout,
                           requires_email)
  else:
    return KeystoneV2Users(auth_url, admin_username, admin_password, admin_tenant, timeout,
                           requires_email)


class KeystoneV2Users(FederatedUsers):
  """ Delegates authentication to OpenStack Keystone V2. """
  def __init__(self, auth_url, admin_username, admin_password, admin_tenant, timeout=None,
               requires_email=True):
    super(KeystoneV2Users, self).__init__('keystone', requires_email)
    self.auth_url = auth_url
    self.admin_username = admin_username
    self.admin_password = admin_password
    self.admin_tenant = admin_tenant
    self.timeout = timeout or DEFAULT_TIMEOUT
    self.debug = os.environ.get('USERS_DEBUG') == '1'
    self.requires_email = requires_email

  def verify_credentials(self, username_or_email, password):
    try:
      keystone_client = kclient.Client(username=username_or_email, password=password,
                                       auth_url=self.auth_url, timeout=self.timeout,
                                       debug=self.debug)
      user_id = keystone_client.user_id
    except KeystoneAuthorizationFailure as kaf:
      logger.exception('Keystone auth failure for user: %s', username_or_email)
      return (None, kaf.message or 'Invalid username or password')
    except KeystoneUnauthorized as kut:
      logger.exception('Keystone unauthorized for user: %s', username_or_email)
      return (None, kut.message or 'Invalid username or password')

    try:
      admin_client = kclient.Client(username=self.admin_username, password=self.admin_password,
                                    tenant_name=self.admin_tenant, auth_url=self.auth_url,
                                    timeout=self.timeout, debug=self.debug)
      user = admin_client.users.get(user_id)
    except KeystoneUnauthorized as kut:
      logger.exception('Keystone unauthorized admin')
      return (None, 'Keystone admin credentials are invalid: %s' % kut.message)

    if self.requires_email and not hasattr(user, 'email'):
      return (None, 'Missing email field for user %s' % user_id)

    email = user.email if hasattr(user, 'email') else None
    return (UserInformation(username=username_or_email, email=email, id=user_id), None)

  def query_users(self, query, limit=20):
    return (None, self.federated_service, 'Unsupported in Keystone V2')

  def get_user(self, username_or_email):
    return (None, 'Unsupported in Keystone V2')


class KeystoneV3Users(FederatedUsers):
  """ Delegates authentication to OpenStack Keystone V3. """
  def __init__(self, auth_url, admin_username, admin_password, admin_tenant, timeout=None,
               requires_email=True):
    super(KeystoneV3Users, self).__init__('keystone', requires_email)
    self.auth_url = auth_url
    self.admin_username = admin_username
    self.admin_password = admin_password
    self.admin_tenant = admin_tenant
    self.timeout = timeout or DEFAULT_TIMEOUT
    self.debug = os.environ.get('USERS_DEBUG') == '1'
    self.requires_email = requires_email

  def verify_credentials(self, username_or_email, password):
    try:
      keystone_client = kv3client.Client(username=username_or_email, password=password,
                                         auth_url=self.auth_url, timeout=self.timeout,
                                         debug=self.debug)
      user_id = keystone_client.user_id
      user = keystone_client.users.get(user_id)

      if self.requires_email and not hasattr(user, 'email'):
        return (None, 'Missing email field for user %s' % user_id)

      return (self._user_info(user), None)
    except KeystoneAuthorizationFailure as kaf:
      logger.exception('Keystone auth failure for user: %s', username_or_email)
      return (None, kaf.message or 'Invalid username or password')
    except KeystoneUnauthorized as kut:
      logger.exception('Keystone unauthorized for user: %s', username_or_email)
      return (None, kut.message or 'Invalid username or password')

  def get_user(self, username_or_email):
    users_found, _, err_msg = self.query_users(username_or_email)
    if err_msg is not None:
      return (None, err_msg)

    if len(users_found) != 1:
      return (None, 'Single user not found')

    user = users_found[0]
    if self.requires_email and not user.email:
      return (None, 'Missing email field for user %s' % user.id)

    return (user, None)

  @staticmethod
  def _user_info(user):
    email = user.email if hasattr(user, 'email') else None
    return UserInformation(user.name, email, user.id)

  def query_users(self, query, limit=20):
    if len(query) < 3:
      return ([], self.federated_service, None)

    try:
      keystone_client = kv3client.Client(username=self.admin_username, password=self.admin_password,
                                         tenant_name=self.admin_tenant, auth_url=self.auth_url,
                                         timeout=self.timeout, debug=self.debug)
      found_users = list(take(limit, keystone_client.users.list(name=query)))
      logger.debug('For Keystone query %s found users: %s', query, found_users)
      if not found_users:
        return ([], self.federated_service, None)

      return ([self._user_info(user) for user in found_users], self.federated_service, None)
    except KeystoneAuthorizationFailure as kaf:
      logger.exception('Keystone auth failure for admin user for query %s', query)
      return (None, self.federated_service, kaf.message or 'Invalid admin username or password')
    except KeystoneUnauthorized as kut:
      logger.exception('Keystone unauthorized for admin user for query %s', query)
      return (None, self.federated_service, kut.message or 'Invalid admin username or password')