const ObjectionToKnexConvertingOperation = require('../ObjectionToKnexConvertingOperation');
const { getTempColumn } = require('../../../utils/tmpColumnUtils');
const { flatten, zipObject } = require('../../../utils/objectUtils');

class WhereInCompositeMsSqlOperation extends ObjectionToKnexConvertingOperation {
  constructor(name, opt) {
    super(name, opt);

    this.prefix = this.opt.prefix || null;
  }

  onBuildKnex(knexBuilder, builder) {
    this.build(builder.knex(), knexBuilder, this.args[0], this.args[1]);
  }

  build(knex, knexBuilder, columns, values) {
    let isCompositeKey = Array.isArray(columns) && columns.length > 1;

    if (isCompositeKey) {
      this.buildComposite(knex, knexBuilder, columns, values);
    } else {
      this.buildNonComposite(knexBuilder, columns, values);
    }
  }

  buildComposite(knex, knexBuilder, columns, values) {
    const helperColumns = columns.map((_, index) => getTempColumn(index));

    if (Array.isArray(values)) {
      this.buildCompositeValue(knex, knexBuilder, columns, helperColumns, values);
    } else {
      this.buildCompositeSubquery(
        knex,
        knexBuilder,
        columns,
        helperColumns,
        values.as(knex.raw(`V(${helperColumns.map(_ => '??')})`, helperColumns))
      );
    }
  }

  buildCompositeValue(knex, knexBuilder, columns, helperColumns, values) {
    return this.buildCompositeSubquery(
      knex,
      knexBuilder,
      columns,
      helperColumns,
      knex.raw(
        `(VALUES ${values
          .map(value => `(${value.map(_ => '?').join(',')})`)
          .join(',')}) AS V(${helperColumns.map(_ => '??').join(',')})`,
        flatten(values).concat(helperColumns)
      )
    );
  }

  buildCompositeSubquery(knex, knexBuilder, columns, helperColumns, subQuery) {
    const wrapperQuery = knex
      .from(subQuery)
      .where(zipObject(helperColumns, columns.map(column => knex.raw('??', column))));

    if (this.prefix === 'not') {
      return knexBuilder.whereNotExists(wrapperQuery);
    } else {
      return knexBuilder.whereExists(wrapperQuery);
    }
  }

  buildNonComposite(knexBuilder, columns, values) {
    const col = typeof columns === 'string' ? columns : columns[0];

    if (Array.isArray(values)) {
      values = pickNonNull(values, []);
    } else {
      values = [values];
    }

    this.whereIn(knexBuilder, col, values);
  }

  whereIn(knexBuilder, col, val) {
    if (this.prefix === 'not') {
      knexBuilder.whereNotIn(col, val);
    } else {
      knexBuilder.whereIn(col, val);
    }
  }
}

function pickNonNull(values, output) {
  for (let i = 0, l = values.length; i < l; ++i) {
    const val = values[i];

    if (Array.isArray(val)) {
      pickNonNull(val, output);
    } else if (val !== null && val !== undefined) {
      output.push(val);
    }
  }

  return output;
}

module.exports = WhereInCompositeMsSqlOperation;
