const Session = require('./Session.js')
const Repository = require('@unitybase/base').ServerRepository.fabric
const ubaCommon = require('@unitybase/base').uba_common
/**
 * UnityBase Row Level Security routines. For use in rls mixin.
 *
 * @author MPV
 * Comment by Felix: *WARNING* On Oracle table alias should not start with '_'
 */

/**
 * @namespace
 */
const RLS = global.RLS = {}
global.$ = RLS

RLS.currentOwner = function () {
  return `( [mi_owner] = :(${Session.userID}): )`
}

/**
 * todo - OPTIMIZE using role cache
 *
 * @deprecated Use functional RLS instead
 * @param {number} user
 * @param {string} groupname
 * @returns {string}
 */
RLS.userInGroup = function (user, groupname) {
  return `exists (select 1 from UBA_USERROLE ur inner join UBA_ROLE r ON ur.RoleID = r.ID WHERE  r.name = :('${groupname}'): AND ur.UserID = :(${user}): )`
}

/**
 * is current ( Session.userID) user have role with name roleName
 *
 * @deprecated Use functional RLS instead
 * @param sender
 * @param roleName
 * @returns {*}
 */
RLS.currentUserInGroup = function (sender, roleName) {
  return Session.hasRole(roleName) ? '(1=1)' : '(0=1)'
}

/**
 * Check user in adm sub-table. No user group check performed!
 *
 * @deprecated Use functional RLS instead
 * @param sender
 * @param user
 */
RLS.userInAdmSubtable = function (sender, user) {
  return `exists (select 1 from ' + sender.entity.name + '_adm uast where uast.instanceID = [ID] and uast.admSubjID = :(${user}): )`
}

RLS.isOracle = function (entity) {
  return entity.connectionConfig.dialect.startsWith('Oracle')
}

/**
 Check user or any of user groups in adm subtable
/*  xmax using ORACLE
 * _todo check oracle syntax!!
 *
 * @deprecated Use functional RLS instead
 * @param sender
 * @param user
 */
RLS.userOrUserGroupInAdmSubtable = function (sender, user) {
  const result = `exists (select 1 from ${sender.entity.name}_adm ast where ast.instanceID = [ID] and ast.admSubjID in (select ur.RoleID from uba_userrole ur where ur.UserID = :(${user}): union select ${user}`
  if (RLS.isOracle(sender.entity)) {
    return result + ' from dual ))'
  } else {
    return result + '))'
  }
}

RLS.currentUserInAdmSubtable = function (sender) {
  return this.userInAdmSubtable(sender, Session.userID)
}

RLS.currentUserOrUserGroupInAdmSubtable = function (sender) {
  let subjects = `ast.admSubjID = :(${Session.userID}):`
  Session.uData.roleIDs.forEach(rID => {
    subjects += ` OR ast.admSubjID = :(${rID}):`
  })
  return `exists (select 1 from ${sender.entity.name}_adm ast where ast.instanceID = [ID] and (${subjects}))`
}

const BY_OWNER_WHERE_LIST_PREDICATE = '__rlsByOwner'
const BY_ADM_WHERE_LIST_PREDICATE = '__rlsByAdm'

/**
 * Returns `true` in case current user is Superuser ( build-in root or admin) or member of Admin group
 *
 * @returns {boolean}
 */
RLS.isSuperUserOrInAdminGroup = function () {
  return (ubaCommon.isSuperUser() || Session.hasRole(ubaCommon.ROLES.ADMIN.NAME))
}
/**
 * For members of Admin group and for users `root` and `admin` do nothing.
 *
 * For other users adds condition what
 *  - either current user is a record owner
 *  - OR user or one of user role in `{$entity}_adm` sub-table
 *
 * @param {ubMethodParams} ctxt
 */
RLS.allowForAdminOwnerAndAdmTable = function (ctxt) {
  // skip RLS for admin and root and Admin group member
  if (RLS.isUserAdminOrInAdminGroup()) return

  const mParams = ctxt.mParams
  let whereList = mParams.whereList
  if (!whereList) {
    mParams.whereList = {}
    // whereList = mParams.whereList = {} assign a {} to whereList instead of TubList instance
    whereList = mParams.whereList
  }
  // either current user is record owner
  whereList[BY_OWNER_WHERE_LIST_PREDICATE] = {
    expression: '[mi_owner]',
    condition: 'equal',
    value: Session.userID
  }
  // or User or one of user role in _adm sub-table
  const eName = ctxt.dataStore.entity.name
  const subQ = Repository(`${eName}_adm`)
    .where('[admSubjID]', 'in', [Session.userID, ...Session.uData.roleIDs, ...Session.uData.groupIDs])
    .correlation('instanceID', 'ID')
    .ubql()
  whereList[BY_ADM_WHERE_LIST_PREDICATE] = {
    expression: '',
    condition: 'subquery',
    subQueryType: 'exists',
    value: subQ
  }
  const logic = `([${BY_OWNER_WHERE_LIST_PREDICATE}]) OR ([${BY_ADM_WHERE_LIST_PREDICATE}])`
  if (!mParams.logicalPredicates) {
    mParams.logicalPredicates = [logic]
  } else {
    const lp = [...mParams.logicalPredicates]
    if (lp.indexOf(logic) === -1) {
      // ubList.push NOT WORK!
      mParams.logicalPredicates = [...mParams.logicalPredicates, logic]
    }
  }
}

/**
 * Returns `true` in case current user is admin or root or Admin group member.
 * Used as default for `aclRls.skipIfFn`
 *
 * @returns {boolean}
 */
RLS.isUserAdminOrInAdminGroup = function () {
  return (ubaCommon.isSuperUser() || Session.hasRole(ubaCommon.ROLES.ADMIN.NAME))
}

/**
 * Default behavior for get aclRls subjects - return array of IDs for currently logged in user:
 *  - if `uba_subject` in onEntities: userID + user roles IDs + user groups IDs
 *  - if `org_unit` in onEntities: orgUnitIDs
 *
 * @param {object} [mixinCfg]
 * @returns {number[]}
 */
RLS.getDefaultAclRlsSubjects = function (mixinCfg) {
  let result = []
  // indexOf is OK here, onEntities length is either 1 or 2
  if (mixinCfg.onEntities.indexOf('uba_subject') >= 0) { // add possible adm subjects
    result = [Session.userID, ...Session.uData.roleIDs, ...Session.uData.groupIDs]
  }
  if (mixinCfg.onEntities.indexOf('org_unit') >= 0) { // add all user org units
    result = result.concat(Session.uData.orgUnitIDs.split(',').map(Number))
  }
  return result
}

/**
 * Helper what validates `RLS.getDefaultAclRlsSubjects` function is applicable for aclRls.onEntities config -
 * check onEntities contains only uba_subject or/and org_unit
 *
 * @param mixinCfg
 * @param entityCode
 */
RLS.getDefaultAclRlsSubjects.validator = function (mixinCfg, entityCode) {
  const admS = new Set(mixinCfg.onEntities)
  admS.delete('uba_subject')
  admS.delete('org_unit')
  if (admS.size !== 0) {
    throw new Error(`AclRls: used '${entityCode}.mixins.aclRls.subjectIDsFn' require 'onEntities' contains 'uba_subject' or|and 'org_unit'`)
  }
}