import sha256 from 'crypto-js/sha256';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import ErrorHandler from '@/utils/ErrorHandler';
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';

/* Uses config accumulator to get and return app config */
const getAppConfig = () => {
  const Accumulator = new ConfigAccumulator();
  const config = Accumulator.config();
  return config.appConfig || {};
};

/**
 * Called when the user is still using array for users, prints warning
 * This was a breaking change, implemented in V 1.6.5
 * Support for old user structure will be removed in V 1.7.0
 */
const printWarning = () => {
  ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
};

/* Returns array of users from appConfig.auth, if available, else an empty array */
const getUsers = () => {
  const appConfig = getAppConfig();
  const auth = appConfig.auth || {};
  // Check if the user is still using previous schema type
  if (Array.isArray(auth)) {
    printWarning(); // Print warning message
    return []; // Support for old data structure now removed
  }
  // Otherwise, return the users array, if available
  return auth.users || [];
};

/**
 * Generates a 1-way hash, in order to be stored in local storage for authentication
 * @param {String} user The username of user
 * @returns {String} The hashed token
 */
const generateUserToken = (user) => {
  if (!user.user || !user.hash) {
    ErrorHandler('Invalid user object. Must have `user` and `hash` parameters');
    return undefined;
  }
  const strAndUpper = (input) => input.toString().toUpperCase();
  const sha = sha256(strAndUpper(user.user) + strAndUpper(user.hash));
  return strAndUpper(sha);
};

/**
 * Checks if the user is currently authenticated
 * @returns {Boolean} Will return true if the user is logged in, else false
 */
export const isLoggedIn = () => {
  const users = getUsers();
  let userAuthenticated = document.cookie.split(';').some((cookie) => {
    if (cookie && cookie.split('=').length > 1) {
      const cookieKey = cookie.split('=')[0].trim();
      const cookieValue = cookie.split('=')[1].trim();
      if (cookieKey === cookieKeys.AUTH_TOKEN) {
        userAuthenticated = users.some((user) => {
          if (generateUserToken(user) === cookieValue) {
            localStorage.setItem(localStorageKeys.USERNAME, user.user);
            return true;
          } else return false;
        });
        return userAuthenticated;
      } else return false;
    } else return false;
  });
  return userAuthenticated;
};

/* Returns true if authentication is enabled */
export const isAuthEnabled = () => {
  const users = getUsers();
  return (users.length > 0);
};

/* Returns true if guest access is enabled */
export const isGuestAccessEnabled = () => {
  const appConfig = getAppConfig();
  if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
    return appConfig.auth.enableGuestAccess || false;
  }
  return false;
};

/**
 * Checks credentials entered by the user against those in the config
 * Returns an object containing a boolean indicating success/ failure
 * along with a message outlining what's not right
 * @param {String} username The username entered by the user
 * @param {String} pass The password entered by the user
 * @param {String[]} users An array of valid user objects
 * @param {Object} messages A static message template object
 * @returns {Object} An object containing a boolean result and a message
 */
export const checkCredentials = (username, pass, users, messages) => {
  let response; // Will store an object containing boolean and message
  if (!username) {
    response = { correct: false, msg: messages.missingUsername };
  } else if (!pass) {
    response = { correct: false, msg: messages.missingPassword };
  } else {
    users.forEach((user) => {
      if (user.user.toLowerCase() === username.toLowerCase()) { // User found
        if (user.hash.toLowerCase() === sha256(pass).toString().toLowerCase()) {
          response = { correct: true, msg: messages.successMsg }; // Password is correct
        } else { // User found, but password is not a match
          response = { correct: false, msg: messages.incorrectPassword };
        }
      }
    });
  }
  return response || { correct: false, msg: messages.incorrectUsername };
};

/**
 * Sets the cookie value in order to login the user locally
 * @param {String} username - The users username
 * @param {String} pass - Password, not yet hashed
 * @param {Number} timeout - A desired timeout for the session, in ms
 */
export const login = (username, pass, timeout) => {
  const now = new Date();
  const expiry = new Date(now.setTime(now.getTime() + timeout)).toGMTString();
  const userObject = { user: username, hash: sha256(pass).toString().toLowerCase() };
  document.cookie = `${cookieKeys.AUTH_TOKEN}=${generateUserToken(userObject)};`
    + `${timeout > 0 ? `expires=${expiry}` : ''}`;
  localStorage.setItem(localStorageKeys.USERNAME, username);
};

/**
 * Removed the browsers' cookie, causing user to be logged out
 */
export const logout = () => {
  document.cookie = `${cookieKeys.AUTH_TOKEN}=null`;
  localStorage.removeItem(localStorageKeys.USERNAME);
};

/**
 * If correctly logged in as a valid, authenticated user,
 * then returns the user object for the current user
 * If not logged in, will return false
 * */
export const getCurrentUser = () => {
  if (!isLoggedIn()) return false; // User not logged in
  const username = localStorage[localStorageKeys.USERNAME]; // Get username
  if (!username) return false; // No username
  let foundUserObject = false; // Value to return
  getUsers().forEach((user) => {
    // If current logged-in user found, then return that user
    if (user.user.toLowerCase() === username.toLowerCase()) foundUserObject = user;
  });
  return foundUserObject;
};

/**
 * Checks if the user is viewing the dashboard as a guest
 * Returns true if guest mode enabled, and user not logged in
 * */
export const isLoggedInAsGuest = (currentUser) => {
  const guestEnabled = isGuestAccessEnabled();
  const loggedIn = isLoggedIn() && currentUser;
  return guestEnabled && !loggedIn;
};

/**
 * Checks if the current user has admin privileges.
 * If no users are set up, then function will always return true
 * But if auth is configured, then will verify user is correctly
 * logged in and then check weather they are of type admin, and
 * return false if any conditions fail
 * @returns {Boolean} - True if admin privileges
 */
export const isUserAdmin = () => {
  const users = getUsers();
  if (users.length === 0) return true; // Authentication not setup
  if (!isLoggedIn()) return false; // Auth setup, but not signed in as a valid user
  const currentUser = localStorage[localStorageKeys.USERNAME];
  let isAdmin = false;
  users.forEach((user) => {
    if (user.user.toLowerCase() === currentUser.toLowerCase()) {
      if (user.type === 'admin') isAdmin = true;
    }
  });
  return isAdmin;
};

/**
  * Determines which button should display, based on the user type
  * 0 = Auth not configured (don't show anything)
  * 1 = Auth configured, and user logged in (show logout button)
  * 2 = Auth configured, guest access enabled, not logged in (show login)
  * Note that if auth is enabled, but not guest access, and user not logged in,
  * then they will never be able to view the homepage, so no button needed
  */
export const getUserState = () => {
  const {
    notConfigured,
    loggedIn,
    guestAccess,
    keycloakEnabled,
  } = userStateEnum; // Numeric enum options
  if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
  if (!isAuthEnabled()) return notConfigured; // No auth enabled
  if (isLoggedIn()) return loggedIn; // User is logged in
  if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing
  return notConfigured;
};
