#!/usr/bin/env node

/**
 * @file
 * Helper functions for syncing Freshdesk knowledge base articles with Transifex
 */

const FreshdeskApi = require('./freshdesk-api.js')
const fs = require('fs')
const fsPromises = fs.promises
const mkdirp = require('mkdirp')
const { txPull, txResourcesObjects, txAvailableLanguages } = require('../lib/transifex.js')

const FD = new FreshdeskApi('https://mitscratch.freshdesk.com', process.env.FRESHDESK_TOKEN)
const TX_PROJECT = 'scratch-help'

const freshdeskLocale = locale => {
  // map between Transifex locale and Freshdesk. Two letter codes are usually fine
  const localeMap = {
    es_419: 'es-LA',
    ja: 'ja-JP',
    'ja-Hira': 'ja-JP',
    lv: 'lv-LV',
    nb: 'nb-NO',
    nn: 'nb-NO',
    pt: 'pt-PT',
    pt_BR: 'pt-BR',
    ru: 'ru-RU',
    sv: 'sv-SE',
    zh_CN: 'zh-CN',
    zh_TW: 'zh-TW',
  }
  return localeMap[locale] || locale
}

/**
 * Pull metadata from Transifex for the scratch-help project
 * @returns {Promise} results array containing:
 *                      languages: array of supported languages
 *                      folders: array of tx resources corresponding to Freshdesk folders
 *                      names: array of tx resources corresponding to the Freshdesk metadata
 */
exports.getInputs = async () => {
  const resources = await txResourcesObjects(TX_PROJECT)
  const languages = await txAvailableLanguages(TX_PROJECT)
  // there are three types of resources differentiated by the file type
  const folders = resources.filter(resource => resource.i18n_type === 'STRUCTURED_JSON')
  const names = resources.filter(resource => resource.i18n_type === 'KEYVALUEJSON')
  // ignore the yaml type because it's not possible to update via API

  return Promise.all([languages, folders, names])
}

/*
 * internal function to serialize saving category and folder name translations to avoid Freshdesk rate limit
 */
const serializeNameSave = async (json, resource, locale) => {
  for (const [key, value] of Object.entries(json)) {
    // key is of the form <name>_<id>
    const words = key.split('_')
    const id = words[words.length - 1]
    let status = 0
    if (resource.name === 'categoryNames_json') {
      status = await FD.updateCategoryTranslation(id, freshdeskLocale(locale), { name: value })
    }
    if (resource.name === 'folderNames_json') {
      status = await FD.updateFolderTranslation(id, freshdeskLocale(locale), { name: value })
    }
    if (status === -1) {
      process.exitCode = 1
    }
  }
}

/**
 * Internal function serialize Freshdesk requests to avoid getting rate limited
 * @param  {object}  json   object with keys corresponding to article ids
 * @param  {string}  locale language code
 * @returns {Promise}        [description]
 */
const serializeFolderSave = async (json, locale) => {
  // json is a map of articles:
  // {
  //   <id>: {
  //     title: {string: <title-value>},
  //     description: {string: <description-value>},
  //     tags: {string: <comma separated strings} // optional
  //   },
  //   <id>: {
  //     title: {string: <title-value>},
  //     description: {string: <description-value>},
  //     tags: {string: <comma separated strings} // optional
  //   }
  // }
  for (const [id, value] of Object.entries(json)) {
    const body = {
      title: value.title.string,
      description: value.description.string,
      status: 2, // set status to published
    }
    if (Object.prototype.hasOwnProperty.call(value, 'tags')) {
      const tags = value.tags.string.split(',')
      const validTags = tags.filter(tag => tag.length < 33)
      if (validTags.length !== tags.length) {
        process.stdout.write(`Warning: tags too long in ${id} for ${locale}\n`)
      }
      body.tags = validTags
    }
    const status = await FD.updateArticleTranslation(id, freshdeskLocale(locale), body)
    if (status === -1) {
      // eslint-disable-next-line require-atomic-updates -- I promise that `process` won't change across `await`
      process.exitCode = 1
    }
  }
  return 0
}

/**
 * Process Transifex resource corresponding to a Knowledge base folder on Freshdesk
 * @param  {object}  folder Transifex resource json corresponding to a KB folder
 * @param  {string}  locale locale to pull and submit to Freshdesk
 * @returns {Promise}        [description]
 */
exports.localizeFolder = async (folder, locale) => {
  txPull(TX_PROJECT, folder.slug, locale, { mode: 'default' })
    .then(data => {
      serializeFolderSave(data, locale)
    })
    .catch(e => {
      process.stdout.write(`Error processing ${folder.slug}, ${locale}: ${e.message}\n`)
      process.exitCode = 1 // not ok
    })
}

/**
 * Save Transifex resource corresponding to a Knowledge base folder locally for debugging
 * @param  {object}  folder Transifex resource json corresponding to a KB folder
 * @param  {string}  locale locale to pull and save
 * @returns {Promise}        [description]
 */
exports.debugFolder = async (folder, locale) => {
  mkdirp.sync('tmpDebug')
  txPull(TX_PROJECT, folder.slug, locale, { mode: 'default' })
    .then(data => {
      fsPromises.writeFile(`tmpDebug/${folder.slug}_${locale}.json`, JSON.stringify(data, null, 2))
    })
    .catch(e => {
      process.stdout.write(`Error processing ${folder.slug}, ${locale}: ${e.message}\n`)
      process.exitCode = 1 // not ok
    })
}

/**
 * Process KEYVALUEJSON resources from scratch-help on transifex
 * Category and Folder names are stored as plain json
 * @param  {object}  resource Transifex resource json for either CategoryNames or FolderNames
 * @param  {string}  locale   locale to pull and submit to Freshdesk
 * @returns {Promise}          [description]
 */
exports.localizeNames = async (resource, locale) => {
  txPull(TX_PROJECT, resource.slug, locale, { mode: 'default' })
    .then(data => {
      serializeNameSave(data, resource, locale)
    })
    .catch(e => {
      process.stdout.write(`Error saving ${resource.slug}, ${locale}: ${e.message}\n`)
      process.exitCode = 1 // not ok
    })
}

const BATCH_SIZE = 2
/*
 * save resource items in batches to reduce rate limiting errors
 * @param  {object}  item      Transifex resource json, used for 'slug'
 * @param  {array}  languages  Array of languages to save
 * @param  {function}  saveFn  Async function to use to save the item
 * @return {Promise}
 */
exports.saveItem = async (item, languages, saveFn) => {
  const saveLanguages = languages.filter(l => l !== 'en') // exclude English from update
  let batchedPromises = Promise.resolve()
  for (let i = 0; i < saveLanguages.length; i += BATCH_SIZE) {
    batchedPromises = batchedPromises
      .then(() => Promise.all(saveLanguages.slice(i, i + BATCH_SIZE).map(l => saveFn(item, l))))
      .catch(err => {
        process.stdout.write(`Error saving item:${err.message}\n${JSON.stringify(item, null, 2)}\n`)
        process.exitCode = 1 // not ok
      })
  }
}