import { extractKeys } from '~/utils/object';

import { getTxOrText } from './translate';
import { TranslationKey, translationKeys } from './translation-keys';
import { es, en, fr } from './translations';

const { exec } = require('child_process');

// Use this array for keys that for whatever reason aren't greppable so they
// don't hold your test suite hostage by always failing.
const EXCEPTIONS = [
  // "welcomeScreen.readyForLaunch",
];

/* eslint-disable no-prototype-builtins */
function iterate(obj, stack, array) {
  for (const property in obj) {
    if (obj.hasOwnProperty(property)) {
      if (typeof obj[property] === 'object') {
        iterate(obj[property], `${stack}.${property}`, array);
      } else {
        array.push(`${stack.slice(1)}.${property}`);
      }
    }
  }
  return array;
}
/* eslint-enable no-prototype-builtins */

describe('i18n', function () {
  const languages = [
    {
      keys: es,
      name: 'es',
    },
    {
      keys: en,
      name: 'en',
    },
    {
      keys: fr,
      name: 'fr',
    },
  ];
  const translations = [
    ['es', es],
    ['fr', fr],
  ];

  it.each(translations)(
    'Translations keys are the same for "en" and "%s" language',
    function (key, language) {
      const translationKeysEn = iterate(en, '', []);
      const translatedKeys = iterate(language, '', []);
      expect(translatedKeys).toEqual(translationKeysEn);
    },
  );

  /**
   * This tests your codebase for missing i18n strings so you can avoid error strings at render time
   *
   * It was taken from https://gist.github.com/Michaelvilleneuve/8808ba2775536665d95b7577c9d8d5a1
   * and modified slightly to account for our Bowser higher order ~/components,
   * which take 'tx' and 'fooTx' props.
   * The grep command is nasty looking, but it's essentially searching the codebase for 3 things:
   *
   * tx="storybook.loremIpsum"
   * Tx="storybook.loremIpsum"
   * translate("storybook.loremIpsum"
   *
   * and then grabs the i18n key between the double quotes
   *
   * This approach isn't 100% perfect. If you are storing your key string in a variable because you
   * are setting it conditionally, then it won't be picked up.
   *
   */
  languages.forEach(function (language) {
    it(`There are no missing keys for ${language.name}`, (done) => {
      // Actual command output:
      // grep "Tx=\"\S*\"\|tx=\"\S*\"\|translate(\"\S*\"" -ohr 'app' --include=*.{js,jsx,ts,tsx} | grep -o "\".*\""
      const command =
        'grep "Tx=\\"\\S*\\"\\|tx=\\"\\S*\\"\\|translate(\\"\\S*\\"" -ohr \'app\' --include=*.{js,jsx,ts,tsx} | grep -o "\\".*\\""';
      exec(command, (_, stdout) => {
        const allTranslationsDefined = iterate(language.keys, '', []);
        const allTranslationsUsed = stdout.replace(new RegExp('"', 'g'), '').split('\n');
        allTranslationsUsed.splice(-1, 1);

        for (let i = 0; i < allTranslationsUsed.length; i += 1) {
          if (!EXCEPTIONS.includes(allTranslationsUsed[i])) {
            // You can add keys to EXCEPTIONS (above) if you don't want them included in the test
            expect(allTranslationsDefined).toContainEqual(allTranslationsUsed[i]);
          }
        }
        done();
      });
    }, 240000);
  });

  it('Validate translations keys', function () {
    expect(extractKeys(en)).toStrictEqual(translationKeys);
  });

  describe('getTxOrText', function () {
    const texts = ['lorem', 'ipsum'];
    const txs: TranslationKey[] = ['common.ok', 'common.cancel'];

    describe('with a non translatable list', function () {
      it('with undefined value', function () {
        expect(getTxOrText(undefined, [], [])).toStrictEqual([]);
      });

      it('with defined value', function () {
        expect(getTxOrText(texts, [], [])).toStrictEqual([
          {
            text: 'lorem',
          },
          {
            text: 'ipsum',
          },
        ]);
      });
    });

    describe('with a translatable list', function () {
      it('undefined', function () {
        expect(getTxOrText([], undefined, [])).toStrictEqual([]);
      });

      it('without options', function () {
        expect(getTxOrText([], txs, [])).toStrictEqual([
          {
            tx: 'common.ok',
            txOptions: undefined,
          },
          {
            tx: 'common.cancel',
            txOptions: undefined,
          },
        ]);
      });

      it('with one option', function () {
        expect(getTxOrText([], txs, [{ paramA: 'valueA' }])).toStrictEqual([
          {
            tx: 'common.ok',
            txOptions: {
              paramA: 'valueA',
            },
          },
          {
            tx: 'common.cancel',
            txOptions: undefined,
          },
        ]);
      });
    });

    it('with a translatable and non translatable list', function () {
      expect(getTxOrText(texts, txs, [])).toStrictEqual([
        {
          tx: 'common.ok',
          txOptions: undefined,
        },
        {
          tx: 'common.cancel',
          txOptions: undefined,
        },
      ]);
    });
  });
});
