import { PromisePoolExecutor } from 'promise-pool-executor';
import { flatten } from '../utils';

const API_CONCURRENCY = 3;
// To ensure a complete deployment, limit the API requests generated to be 80% of the capacity
// https://auth0.com/docs/policies/rate-limits#management-api-v2
const API_FREQUENCY_PER_SECOND = 8;

const MAX_PAGE_SIZE = 100;

function getEntity(rsp) {
  const found = Object.values(rsp).filter(a => Array.isArray(a));
  if (found.length === 1) {
    return found[0];
  }
  throw new Error('There was an error trying to find the entity within paginate');
}

// Warp around a <resource>Manager and detect when requesting specific pages to return all
function pagedManager(client, manager) {
  return new Proxy(manager, {
    get: function(target, name, receiver) {
      if (name === 'getAll') {
        return async function(...args) {
          // If the function was called with object params and paginate (entity) then handle pagination
          if (args[0] && typeof args[0] === 'object' && args[0].paginate) {
            const fnName = name;

            // Where the entity data will be collected
            const data = [];

            // Create new args and inject the properties we require for pagination automation
            const newArgs = [ ...args ];
            newArgs[0] = { ...newArgs[0], include_totals: true, page: 0 };

            // Grab data we need from the request then delete the keys as they are only needed for this automation function to work
            const perPage = newArgs[0].per_page || MAX_PAGE_SIZE;
            newArgs[0].per_page = perPage;
            delete newArgs[0].paginate;

            // Run the first request to get the total number of entity items
            const rsp = await target[fnName](...newArgs);
            data.push(...getEntity(rsp));
            const total = rsp.total || 0;
            const pagesLeft = Math.ceil(total / perPage) - 1;

            // Setup pool to get the rest of the pages
            if (pagesLeft > 0) {
              await client.pool.addEachTask({
                data: Array.from(Array(pagesLeft).keys()),
                generator: (page) => {
                  const pageArgs = [ ...newArgs ];
                  pageArgs[0].page = page + 1;
                  return target[fnName](...pageArgs).then((r) => {
                    data.push(...getEntity(r));
                  });
                }
              }).promise().then(results => data.push(...flatten(results)));
            }
            return data;
          }
          return target[name](...args);
        };
      }

      return Reflect.get(target, name, receiver);
    }
  });
}

// Warp around the ManagementClient and detect when requesting specific pages to return all
export default function pagedClient(client) {
  client.pool = new PromisePoolExecutor({
    concurrencyLimit: API_CONCURRENCY,
    frequencyLimit: API_FREQUENCY_PER_SECOND,
    frequencyWindow: 1000 // 1 sec
  });

  return new Proxy(client, {
    get: function(target, name, receiver) {
      if (name in target && target[name].getAll) {
        return pagedManager(client, target[name]);
      }
      return Reflect.get(target, name, receiver);
    }
  });
}

@@ -89,16 +89,40 @@
 /utils';
+%0Aimport _ from %22lodash%22;
 %0A%0Aconst 
@@ -1949,17 +1949,16 @@
 e) - 1;%0A
-%0A
         
@@ -2050,16 +2050,30 @@
         
+ const pages =
  await c
@@ -2205,32 +2205,34 @@
                 
+  
 const pageArgs =
@@ -2236,24 +2236,32 @@
 s = 
-%5B ...
+_.cloneDeep(
 newArgs
- %5D;%0A
+);%0A  
     
@@ -2299,24 +2299,26 @@
 = page + 1;%0A
+%0A 
             
@@ -2315,32 +2315,33 @@
                 
+ 
 return target%5Bfn
@@ -2368,16 +2368,27 @@
 hen(
-(r)
+r
  =%3E 
-%7B
+getEntity(r));
 %0A   
@@ -2404,40 +2404,73 @@
     
+%7D%0A
     
-data.push(...getEntity(r
+          %7D).promise().then(results =%3E flatten(results
 ));%0A
+%0A
     
@@ -2483,16 +2483,30 @@
     
-    %7D
+data.push(...pages
 );%0A
+%0A
     
@@ -2519,87 +2519,118 @@
     
-  %7D%0A              %7D).promise().then(results =%3E data.push(...flatten(results)));
+if (data.length !== total) %7B%0A                throw new Error(%22Fail to load data from tenant%22);%0A              %7D
 %0A   

Fix missing pages