import logging
import os
import itertools

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

logger = logging.getLogger(__name__)

DEFAULT_TIMEOUT = 10 # seconds

def _take(n, iterable):
  "Return first n items of the iterable as a list"
  return list(itertools.islice(iterable, n))


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, '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 ([], 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 ([], None)

      return ([self._user_info(user) for user in found_users], None)
    except KeystoneAuthorizationFailure as kaf:
      logger.exception('Keystone auth failure for admin user for query %s', query)
      return (None, 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, kut.message or 'Invalid admin username or password')