'use strict';

const debug = require('debug')('google-play-scraper:reviewsMappedRequests');
const R = require('ramda');
const request = require('../utils/request');
const scriptData = require('../utils/scriptData');
const reviewsList = require('../utils/reviewsList');
const { REQUEST_MAPPINGS } = require('../mappers/reviews');
const { BASE_URL } = require('../utils/configurations');

/**
 * Format the reviews for correct and unified response model
 * @param {array} reviews The reviews to be formated
 * @param {string} token The token to be sent
 */
function formatReviewsResponse ({
  reviews,
  num,
  token = null
}) {
  const reviewsToResponse = (reviews.length >= num)
    ? reviews.slice(0, num)
    : reviews;

  return {
    data: reviewsToResponse,
    nextPaginationToken: token
  };
}

/**
 * This object allow us to differ between
 * the initial body request and the paginated ones
 */
const REQUEST_TYPE = {
  initial: 'initial',
  paginated: 'paginated'
};

/**
 * This method allow us to get the body for the review request
 *
 * @param {string} options.appId The app id for reviews
 * @param {number} options.sort The sort order for reviews
 * @param {number} options.numberOfReviewsPerRequest The number of reviews per request
 * @param {string} options.withToken The token to be used for the given request
 * @param {string} options.requestType The request type
 */
function getBodyForRequests ({
  appId,
  sort,
  numberOfReviewsPerRequest = 150,
  withToken = '%token%',
  requestType = REQUEST_TYPE.initial
}) {
  /* The body is slight different for the initial and paginated requests */
  const formBody = {
    [REQUEST_TYPE.initial]: `f.req=%5B%5B%5B%22UsvDTd%22%2C%22%5Bnull%2Cnull%2C%5B2%2C${sort}%2C%5B${numberOfReviewsPerRequest}%2Cnull%2Cnull%5D%2Cnull%2C%5B%5D%5D%2C%5B%5C%22${appId}%5C%22%2C7%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D`,
    [REQUEST_TYPE.paginated]: `f.req=%5B%5B%5B%22UsvDTd%22%2C%22%5Bnull%2Cnull%2C%5B2%2C${sort}%2C%5B${numberOfReviewsPerRequest}%2Cnull%2C%5C%22${withToken}%5C%22%5D%2Cnull%2C%5B%5D%5D%2C%5B%5C%22${appId}%5C%22%2C7%5D%5D%22%2Cnull%2C%22generic%22%5D%5D%5D`
  };

  return formBody[requestType];
}

async function processReviewsAndGetNextPage (html, opts, savedReviews) {
  const processAndRecurOptions = Object.assign({}, opts, { requestType: REQUEST_TYPE.paginated });
  const { appId, paginate, num } = processAndRecurOptions;
  const parsedHtml = R.is(String, html)
    ? scriptData.parse(html)
    : html;

  if (parsedHtml.length === 0) {
    return formatReviewsResponse(savedReviews);
  }

  // PROCESS REVIEWS EXTRACTION
  const reviews = reviewsList.extract(REQUEST_MAPPINGS.reviews, parsedHtml, appId);
  const token = R.path(REQUEST_MAPPINGS.token, parsedHtml);
  const reviewsAccumulator = [...savedReviews, ...reviews];

  return (!paginate && token)
    ? makeReviewsRequest(processAndRecurOptions, reviewsAccumulator, token)
    : formatReviewsResponse({ reviews, token, num });
}

/**
 * Make a review request to Google Play Store
 * @param {object} opts The request options
 * @param {array} savedReviews The reviews accumulator array
 * @param {string} nextToken The next page token
 */
function makeReviewsRequest (opts, savedReviews, nextToken) {
  debug('nextToken: %s', nextToken);
  debug('savedReviews length: %s', savedReviews.length);
  debug('requestType: %s', opts.requestType);

  const {
    appId,
    sort,
    requestType,
    lang,
    country,
    requestOptions,
    throttle
  } = opts;
  const body = getBodyForRequests({
    appId,
    sort,
    withToken: nextToken,
    requestType
  });
  const url = `${BASE_URL}/_/PlayStoreUi/data/batchexecute?rpcids=qnKhOb&f.sid=-697906427155521722&bl=boq_playuiserver_20190903.08_p0&hl=${lang}&gl=${country}&authuser&soc-app=121&soc-platform=1&soc-device=1&_reqid=1065213`;

  debug('batchexecute URL: %s', url);
  debug('with body: %s', body);

  const reviewRequestOptions = Object.assign({
    url,
    method: 'POST',
    body,
    followAllRedirects: true,
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
    }
  }, requestOptions);

  return request(reviewRequestOptions, throttle)
    .then((html) => {
      const input = JSON.parse(html.substring(5));
      const data = JSON.parse(input[0][2]);

      return (data === null)
        ? formatReviewsResponse(savedReviews)
        : processReviewsAndGetNextPage(data, opts, savedReviews);
    });
}

/**
 * Process the reviews for a given app
 * @param {object} opts The options for reviews behavior
 */
function processReviews (opts) {
  const requestType = (!opts.nextPaginationToken)
    ? REQUEST_TYPE.initial
    : REQUEST_TYPE.paginated;
  const token = opts.nextPaginationToken || '%token%';

  const reviewsOptions = Object.assign({}, { requestType }, opts);
  return makeReviewsRequest(reviewsOptions, [], token);
}

module.exports = {
  processReviews
};

@@ -2825,24 +2825,64 @@
 ate && token
+ && reviewsAccumulator.length %3C opts.num
 )%0A    ? make

Fix .reviews method (#423)