'use strict';

const _ = require('lodash');
const { yup, formatYupErrors } = require('strapi-utils');
const validators = require('./common-validators');

const handleReject = error => Promise.reject(formatYupErrors(error));

// validatedUpdatePermissionsInput

const BOUND_ACTIONS = [
  'plugins::content-manager.explorer.read',
  'plugins::content-manager.explorer.create',
  'plugins::content-manager.explorer.update',
  'plugins::content-manager.explorer.delete',
];

const checkPermissionsAreBound = function(permissions) {
  const subjectMap = {};
  let areBond = true;
  permissions
    .filter(perm => BOUND_ACTIONS.includes(perm.action))
    .forEach(perm => {
      subjectMap[perm.subject] = subjectMap[perm.subject] || {};
      perm.fields.forEach(field => {
        subjectMap[perm.subject][field] = subjectMap[perm.subject][field] || new Set();
        subjectMap[perm.subject][field].add(perm.action);
      });
    });

  _.forIn(subjectMap, subject => {
    _.forIn(subject, field => {
      if (field.size !== BOUND_ACTIONS.length) {
        areBond = false;
        return false;
      }
    });
    if (!areBond) return false;
  });

  return areBond;
};

const updatePermissionsSchemaWithBoundConstraint = [
  validators.updatePermissions,
  yup.object().shape({
    permissions: yup
      .array()
      .test(
        'are-bond',
        'Read, Create, Update and Delete have to be defined all together for a subject field or not at all',
        checkPermissionsAreBound
      ),
  }),
];

const checkPermissionsSchema = yup.object().shape({
  permissions: yup.array().of(
    yup
      .object()
      .shape({
        action: yup.string().required(),
        subject: yup.string(),
        field: yup.string(),
      })
      .noUnknown()
  ),
});

const validateCheckPermissionsInput = data => {
  return checkPermissionsSchema
    .validate(data, { strict: true, abortEarly: false })
    .catch(handleReject);
};

const validatedUpdatePermissionsInput = async data => {
  try {
    for (const schema of updatePermissionsSchemaWithBoundConstraint) {
      await schema.validate(data, { strict: true, abortEarly: false });
    }
  } catch (e) {
    return handleReject(e);
  }
};

// validatePermissionsExist

const checkPermissionsExist = function(permissions) {
  const existingActions = strapi.admin.services.permission.actionProvider.getAll();
  const failIndex = permissions.findIndex(
    permission =>
      !existingActions.some(
        ea =>
          ea.actionId === permission.action &&
          (ea.section !== 'contentTypes' || ea.subjects.includes(permission.subject))
      )
  );

  return failIndex === -1
    ? true
    : this.createError({
        path: 'permissions',
        message: `[${failIndex}] is not an existing permission action`,
      });
};

const actionsExistSchema = yup
  .array()
  .of(
    yup.object().shape({
      conditions: validators.arrayOfConditionNames,
    })
  )
  .test('actions-exist', '', checkPermissionsExist);

const validatePermissionsExist = data => {
  return actionsExistSchema.validate(data, { strict: true, abortEarly: false }).catch(handleReject);
};

// exports

module.exports = {
  validatedUpdatePermissionsInput,
  validatePermissionsExist,
  validateCheckPermissionsInput,
};

@@ -527,53 +527,66 @@
 %7B%0A  
-const subjectMap = %7B%7D;%0A  let areBond = true;%0A
+let areBond = true;%0A  const permsBySubject = _.groupBy(%0A  
   pe
@@ -594,21 +594,16 @@
 missions
-%0A    
 .filter(
@@ -650,414 +650,369 @@
 on))
+,
 %0A
-    .forEach(perm =%3E %7B%0A  
     
+'
 subject
-Map%5Bperm.subject%5D = subjectMap%5Bperm.subject%5D %7C%7C %7B%7D;%0A      perm.fields.forEach(field =%3E %7B%0A        subjectMap%5Bperm.subject%5D%5Bfield%5D = subjectMap%5Bperm.subject%5D%5Bfield%5D %7C%7C new Set();%0A        subjectMap%5Bperm.subject%5D%5Bfield%5D.add(perm.
+'%0A  );%0A  _.forIn(permsBySubject, perms =%3E %7B%0A    const uniqPerms = _.uniqBy(perms, 'action');%0A    areBond = uniqPerms.length === BOUND_ACTIONS.length;%0A    if (!areBond) return false;%0A    const permsByAction = _.groupBy(uniqPerms, '
 action
+'
 );%0A    
-  %7D);%0A    %7D);%0A%0A  _.forIn(subjectMap, subject =%3E %7B%0A    _.forIn(subject, field =%3E %7B%0A      if (field.size !== BOUND_ACTIONS.length) %7B
+areBond =%0A      BOUND_ACTIONS.slice(0, 3)%0A        .map(action =%3E permsByAction%5Baction%5D%5B0%5D.fields %7C%7C %5B%5D)
 %0A   
@@ -1020,24 +1020,27 @@
     
-areBond
+.map(f
  =
+%3E
  f
-alse;
+.sort())
 %0A   
@@ -1048,36 +1048,75 @@
     
-return false;%0A      %7D%0A    %7D)
+.filter((fields, i, arr) =%3E _.isEqual(arr%5B0%5D, fields)).length === 3
 ;%0A  

handle fields null for delete perm