import {slug, getLdapUsername, getLdapEmail, getLdapUserUniqueID, syncUserData, addLdapUser} from './sync';
import LDAP from './ldap';
import { log_debug, log_info, log_warn, log_error } from './logger';

function fallbackDefaultAccountSystem(bind, username, password) {
  if (typeof username === 'string') {
    if (username.indexOf('@') === -1) {
      username = {username};
    } else {
      username = {email: username};
    }
  }

  log_info('Fallback to default account system: ', username );

  const loginRequest = {
    user: username,
    password: {
      digest: SHA256(password),
      algorithm: 'sha-256',
    },
  };
  log_debug('Fallback options: ', loginRequest);

  return Accounts._runLoginHandlers(bind, loginRequest);
}

Accounts.registerLoginHandler('ldap', function(loginRequest) {
  if (!loginRequest.ldap || !loginRequest.ldapOptions) {
    return undefined;
  }

  log_info('Init LDAP login', loginRequest.username);

  if (LDAP.settings_get('LDAP_ENABLE') !== true) {
    return fallbackDefaultAccountSystem(this, loginRequest.username, loginRequest.ldapPass);
  }

  const self = this;
  const ldap = new LDAP();
  let ldapUser;

  try {

      ldap.connectSync();

     if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) {
        ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass);
       ldapUser = ldap.searchUsersSync(loginRequest.username)[0];
       } else {

       const users = ldap.searchUsersSync(loginRequest.username);

       if (users.length !== 1) {
         log_info('Search returned', users.length, 'record(s) for', loginRequest.username);
         throw new Error('User not Found');
       }

       if (ldap.authSync(users[0].dn, loginRequest.ldapPass) === true) {
         if (ldap.isUserInGroup(loginRequest.username, users[0])) {
           ldapUser = users[0];
         } else {
           throw new Error('User not in a valid group');
         }
       } else {
         log_info('Wrong password for', loginRequest.username);
       }
     }


  } catch (error) {
     log_error(error);
  }

  if (!ldapUser) {
    if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
      return fallbackDefaultAccountSystem(self, loginRequest.username, loginRequest.ldapPass);
    }

    throw new Meteor.Error('LDAP-login-error', `LDAP Authentication failed with provided username [${ loginRequest.username }]`);
  }

  // Look to see if user already exists

  let userQuery;

  const Unique_Identifier_Field = getLdapUserUniqueID(ldapUser);
  let user;
   // Attempt to find user by unique identifier

  if (Unique_Identifier_Field) {
    userQuery = {
      'services.ldap.id': Unique_Identifier_Field.value,
    };

    log_info('Querying user');
    log_debug('userQuery', userQuery);

    user = Meteor.users.findOne(userQuery);
   }

  // Attempt to find user by username

  let username;
  let email;

   if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') {
    username = slug(getLdapUsername(ldapUser));
  } else {
    username = slug(loginRequest.username);
  }

  if(LDAP.settings_get('LDAP_EMAIL_FIELD') !== '') {
    email = getLdapEmail(ldapUser);
  }


  if (!user) {
    if(email && LDAP.settings_get('LDAP_EMAIL_MATCH_REQUIRE') === true) {
      if(LDAP.settings_get('LDAP_EMAIL_MATCH_VERIFIED') === true) {
        userQuery = {
          '_id' : username,
          'emails.0.address' : email,
          'emails.0.verified' : true
        };
      } else {
        userQuery = {
          '_id' : username,
          'emails.0.address' : email
        };
      }
    } else {
      userQuery = {
        username
      };
    }

    log_debug('userQuery', userQuery);

    user = Meteor.users.findOne(userQuery);
  }

  // Attempt to find user by e-mail address only

  if (!user && email && LDAP.settings_get('LDAP_EMAIL_MATCH_ENABLE') === true) {

    log_info('No user exists with username', username, '- attempting to find by e-mail address instead');

    if(LDAP.settings_get('LDAP_EMAIL_MATCH_VERIFIED') === true) {
      userQuery = {
        'emails.0.address': email,
        'emails.0.verified' : true
      };
    } else {
      userQuery = {
        'emails.0.address' : email
      };
    }

    log_debug('userQuery', userQuery);

    user = Meteor.users.findOne(userQuery);

  }

  // Login user if they exist
  if (user) {
    if (user.authenticationMethod !== 'ldap' && LDAP.settings_get('LDAP_MERGE_EXISTING_USERS') !== true) {
      log_info('User exists without "authenticationMethod : ldap"');
      throw new Meteor.Error('LDAP-login-error', `LDAP Authentication succeded, but there's already a matching Wekan account in MongoDB`);
    }

    log_info('Logging user');

    const stampedToken = Accounts._generateStampedLoginToken();
    const update_data = {
      $push: {
        'services.resume.loginTokens': Accounts._hashStampedToken(stampedToken),
      },
    };

    if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
      log_debug('Updating admin status');
      const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
      const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));

      user.isAdmin = groups.length > 0;
      Meteor.users.update({_id: user._id}, {$set: {isAdmin: user.isAdmin}});
    }

    if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
      log_debug('Updating Groups/Roles');
      const groups = ldap.getUserGroups(username, ldapUser);

      if( groups.length > 0 ) {
        Roles.setUserRoles(user._id, groups );
        log_info(`Updated roles to:${  groups.join(',')}`);
      }
    }

    Meteor.users.update(user._id, update_data );

    syncUserData(user, ldapUser);

    if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) {
      Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false});
    }

    return {
      userId: user._id,
      token: stampedToken.token,
    };
  }

  // Create new user

  log_info('User does not exist, creating', username);

  if (LDAP.settings_get('LDAP_USERNAME_FIELD') === '') {
    username = undefined;
  }

  if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') !== true) {
    loginRequest.ldapPass = undefined;
  }

  const result = addLdapUser(ldapUser, username, loginRequest.ldapPass);

  if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) {
    log_debug('Updating admin status');
    const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(',');
    const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value));

    result.isAdmin = groups.length > 0;
    Meteor.users.update({_id: result.userId}, {$set: {isAdmin: result.isAdmin}});
  }

  if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) {
    const groups = ldap.getUserGroups(username, ldapUser);
    if( groups.length > 0 ) {
      Roles.setUserRoles(result.userId, groups );
      log_info(`Set roles to:${  groups.join(',')}`);
    }
  }


  if (result instanceof Error) {
    throw result;
  }

  return result;
});
