/* eslint-disable no-magic-numbers */
/* eslint-disable no-console */

'use strict';

const fs = require ('fs-extra');

/**
 * convert short type notation to knex notation
 *
 * @param {string} short short type
 * @returns {string} type
 */
function get_type (short) {
  switch (short) {
    case '\'\'':
      return 'string';
    case '#':
      return 'integer';
    case '#.#':
      return 'double';
    case '✓':
      return 'boolean';
    case '🖹':
      return 'text';
    case '🕓':
      return 'timestamp';
    default:
      return '';
  }
}

/**
 * returns columns and attributes for a table
 *
 * @param {string} str table definition
 * @returns {object} table info
 */
function get_table_info (str) {
  const lines = str.split (/\n/ug);
  lines.splice (0, 2);
  lines.splice (lines.length - 2, 2);
  const name_line = lines.shift ();
  const { name } = (/<b>(?<name>\S+)<\/b>/u).exec (name_line).groups;
  const columns = [];
  while (lines.length > 0) {
    const col = {};
    const l = lines.shift ();
    const regex = /<tr><td.*?>(?<props>.*?)<\/td><\/tr>/u;
    const data = regex.exec (l).groups.props.split (/\s+/gu);
    if (data.length === 3 || data[0] === '🔑') {
      const opt = data.shift ();
      if (opt === '★')
        col.unique = true;
      if (opt === '🔑')
        col.type = 'increments';
    }

    col.name = data.shift ();
    if (data.length > 0)
      col.type = get_type (data.shift ());

    if (typeof col.type === 'undefined')
      console.error (`column type is undefined: ${col.name} table: ${name}`);

    columns.push (col);
  }

  return { name, columns, foreign_keys: [] };
}

/**
 * get all tables from a structure file
 *
 * @param {string} file path to the structure file
 * @returns {Promise<object>} tables and foreign keys
 */
async function get_tables (file) {
  const lines = (await fs.readFile (file, 'utf-8')).split (/\n/gu);
  const curr = [];
  const tables = [];
  const foreign = [];
  for (const l of lines) {
    if (curr.length > 0 || (/\S+ \[label=</u).test (l))
      curr.push (l);

    if ((/>.*?\]/u).test (l)) {
      const val = curr.join ('\n');
      if (val)
        tables.push (get_table_info (val));
      curr.splice (0, curr.length);
    }

    get_foreign_key (l, foreign);
  }

  for (const fk of foreign) {
    for (let i = 0; i < tables.length; i++) {
      if (tables[i].name === fk.table) {
        tables[i].foreign_keys.push (fk);
        break;
      }
    }
  }

  return tables;
}

/**
 * gets foreign keys from a line
 *
 * @param {string} line line to check
 * @param {Array<object>} foreign_keys array to add to
 */
function get_foreign_key (line, foreign_keys) {
  const fk = (/(?<col>\S+) -> (?<ref>\S+)/u).exec (line);
  if (fk) {
    const col = fk.groups.col.split (':');
    const ref = fk.groups.ref.split (':');

    const foreign_key = {
      table:      col[0],
      column:     col[1],
      ref_table:  ref[0],
      ref_column: ref[1]
    };

    foreign_keys.push (foreign_key);
  }
}

/**
 * creates a function for creating a table
 *
 * @param {object} table table to create a function for
 * @returns {string} function
 */
function create_table_function (table) {
  let func = `/**
 * create table ${table.name}
 *
 * @param {any} knex database connection
 * @returns {Promise} result
 */
function create_${table.name} (knex) {
  return knex.schema.createTable ('${table.name}', (table) => {
    ${table.columns
    .map ((col) => `table.${col.type} ('${col.name}');`)
    .join ('\n    ')}
`;

  const unique = table.columns.filter ((val) => val.unique);
  if (unique.length > 0) {
    func += `\n    table.unique (${unique
      .map ((val) => `'${val.name}'`)
      .join (', ')});\n`;
  }

  if (table.foreign_keys.length > 0) {
    func += '\n';
    for (const fk of table.foreign_keys) {
      func += `    table.foreign ('${fk.column}')
      .references ('${fk.ref_column}')
      .inTable ('${fk.ref_table}');\n`;
    }
  }

  func += `  });
}`;

  return func;
}

/**
 * creates the migration function
 *
 * @param {Array<object>} tables table array
 * @returns {string} function
 */
function create_up_function (tables) {
  const func = `async function up (knex) {
${tables.map ((val) => `  await create_${val.name} (knex);`)
    .join ('\n')}
}`;
  return func;
}

/**
 * creates the complete migration file for a graph
 *
 * @param {string} file_path file to scan
 * @returns {Promise<string>} file
 */
async function create_migration (file_path) {
  const tables = await get_tables (file_path);
  const functions = tables.map ((tab) => create_table_function (tab));

  const file = `'use strict';

${functions.join ('\n\n')}

/**
 * run migration
 *
 * @param {any} knex db connection
 */
${create_up_function (tables)}

/**
 * revert migration
 */
function down () {
  // noop
}

module.exports = { up, down };
`;
  return file;
}

module.exports = { get_tables, create_table_function, create_migration };