const { createSpinner } = require('nanospinner');

const getPartial = require('../../helpers/get-partial');
const print = require('../../helpers/print');
const extractFieldDefinitions = require('../../utils/translations/extractFieldDefinitions');
const extractContentKeys = require('../../utils/translations/extractContentKeys');
const extractPaths = require('../../utils/translations/extractPaths');

const getTouchpointData = require('./getTouchpointData');
const getAllItemManifests = require('./getAllItemManifests');

const DEFAULT_REQUIRED_TRANSLATIONS = [
  'form.email.errors.email',
  'form.email.errors.required',
  'form.first_name.errors.required',
  'form.last_name.errors.required',
];

const leadTranslations = ['flow.start.link', 'flow.error_used_lead.title'];

const REQUEST_SPECIFIC_REQUIRED_TRANSLATIONS = {
  lead: leadTranslations,
  save_quote_for_later: leadTranslations,
  idd: leadTranslations,
};

/**
 * @param {Record<string, any>} flags
 * @param {string[]=} attributes
 * @returns {Promise<Output>}
 */
async function translations({
  host = 'feapi.kasko.io',
  tp,
  key,
  language = 'de',
  silent = false,
  batch = 4,
  delay = 1000,
  output = 'verbose',
  requiredDefaults = DEFAULT_REQUIRED_TRANSLATIONS,
  fromCli = false,
} = {}) {
  if (typeof tp === 'undefined') throw new Error('Translations: flag `tp` must be defined.');
  if (typeof key === 'undefined') throw new Error('Translations: flag `key` must be defined.');
  if (['verbose', 'json'].indexOf(output) < 0)
    throw new Error('Translations: flag `output` supports only "verbose" and "json".');

  const showVerboseOutput = output !== 'verbose' || !fromCli || silent;

  print.silenced(showVerboseOutput);
  function spin(text) {
    if (showVerboseOutput) {
      return {
        success: () => {},
        error: () => {},
        text: null,
      };
    }

    return createSpinner(text).start();
  }

  let spinner;
  const requestParams = { language, key };
  const url = `https://${host}/touchpoints/${tp}`;

  print();
  spinner = spin('Getting touchpoint data');

  const { productName, touchpointTranslations, items, webappManifest } = await getTouchpointData(
    url,
    requestParams,
  ).catch((error) => {
    spinner.error();
    throw error;
  });

  spinner.success();
  spinner = spin(`Getting item data [0/${items.length}]`);

  const itemManifests = await getAllItemManifests(url, requestParams, items, {
    batchSize: batch,
    delayMs: delay,
    spinner: (value) => {
      if (value === false) {
        spinner.error();
        return;
      }

      spinner.text = value;
    },
  }).catch((error) => {
    spinner.error();
    throw error;
  });

  spinner.success();
  spinner = spin('Preparing data');

  const itemList = itemManifests.reduce((acc, item) => {
    const activeRequests = item.webapp_manifest.schema?.config?.requests || [];
    const requestSpecificTranslations = activeRequests
      .map(({ type }) => type)
      .reduce((requestAcc, requestName) => {
        const keys = REQUEST_SPECIFIC_REQUIRED_TRANSLATIONS[requestName];
        if (!keys) return requestAcc;

        return requestAcc.concat(keys);
      }, []);
    const existingItemTranslations = extractPaths(item.content.data);
    const fieldDefinitions = extractFieldDefinitions(item.field_definition).concat(
      ...activeRequests.map(extractFieldDefinitions),
    );
    const itemAndTouchpointTranslations = touchpointTranslations.concat(
      existingItemTranslations.filter((itemKey) => touchpointTranslations.indexOf(itemKey) < 0),
    );
    const usedTranslations = [
      ...new Set(
        extractContentKeys(item.webapp_manifest)
          .concat(extractContentKeys(webappManifest))
          .concat(requiredDefaults)
          .concat(requestSpecificTranslations)
          .concat(fieldDefinitions),
      ),
    ];
    const { missingTranslations, idleTranslations } = usedTranslations.reduce(
      (itemAcc, itemKey) => {
        const value = getPartial(itemKey.split('.'), item.content.data);

        if (itemKey === value || value === '') {
          return {
            ...itemAcc,
            idleTranslations: itemAcc.idleTranslations.concat(itemKey),
          };
        }

        if (itemAndTouchpointTranslations.indexOf(itemKey) < 0) {
          return {
            ...itemAcc,
            missingTranslations: itemAcc.missingTranslations.concat(itemKey),
          };
        }

        return itemAcc;
      },
      {
        missingTranslations: [],
        idleTranslations: [],
      },
    );

    return {
      ...acc,
      [item.id]: {
        contentId: item.content.id,
        missingTranslations,
        idleTranslations,
        usedTranslations,
        fieldDefinitions,
        existingTranslations: itemAndTouchpointTranslations,
      },
    };
  }, {});

  const summaryTemplate = {
    existing: 0,
    used: 0,
    missing: 0,
    idle: 0,
  };

  const summary = Object.keys(itemList).reduce(
    (acc, itemKey) => ({
      existing: acc.existing + itemList[itemKey].existingTranslations.length,
      used: acc.used + itemList[itemKey].usedTranslations.length,
      missing: acc.missing + itemList[itemKey].missingTranslations.length,
      idle: acc.idle + itemList[itemKey].idleTranslations.length,
    }),
    summaryTemplate,
  );

  spinner.success();

  print();
  print(`## Product "${productName}" with ${itemManifests.length} item(s)`);
  print();
  print('# Translation summary:');
  print(`- Existing: __${summary.existing}__`);
  print(`- Missing: __${summary.missing}__`);
  print(`- Idle: __${summary.idle}__`);
  print(`- Unfilled: __${summary.missing}__`);
  print(`- Used: __${summary.used}__`);

  if (summary.missing === 0 && summary.idle === 0) {
    print();
    print('`No missing translations!`');
  } else {
    Object.keys(itemList).forEach((itemId) => {
      if (itemList[itemId].idleTranslations.length !== 0) {
        const formattedIdleKeys = itemList[itemId].idleTranslations
          .map((itemKey) => `"${itemKey}", "${itemKey}"`)
          .join('\n');

        print();
        print(`Unfinished/Idle translation keys for __"${itemId}"__
https://dashboard.kasko.io/contents/view/${itemList[itemId].contentId}\n\n\`${formattedIdleKeys}\``);
      }

      if (itemList[itemId].missingTranslations.length === 0) return;

      const formattedMissingKeys = itemList[itemId].missingTranslations
        .map((itemKey) => `"${itemKey}", "${itemKey}"`)
        .join('\n');

      print();
      print(`Missing translation keys for __"${itemId}"__
https://dashboard.kasko.io/contents/view/${itemList[itemId].contentId}\n\n\`${formattedMissingKeys}\``);
    });
  }

  print();

  const jsonOutput = {
    summary,
    output: itemList,
  };

  if (output === 'json' && fromCli) {
    // Print method won't work here, as it adds unnecessary characters
    process.stdout.write(`${JSON.stringify(jsonOutput)}\n`);
  }

  return Promise.resolve(jsonOutput);
}

module.exports = translations;
module.exports.readme = require('./readme.md');

/**
 * @typedef {Object} Translations
 * @property {string} contentId
 * @property {string[]} fieldDefinitions
 * @property {string[]} usedTranslations
 * @property {string[]} missingTranslations
 * @property {string[]} idleTranslations
 * @property {string[]} existingTranslations
 */

/**
 * @typedef {Object.<string, Translations[]>} ItemList
 */

/**
 * @typedef {Object} Output
 * @property {{existing: number,used: number,missing: number,idle: number}} summary
 * @property {ItemList} output
 */
