'use strict';

const knex = require('../knex');
const logger = require('../logger')(__filename);

async function upsert(table, items, identifier = ['id'], _knex) {
    const identifiers = Array.isArray(identifier) ? identifier : [identifier];

    const duplicates = await knex(table).whereIn(identifiers, items.map(item => identifiers.map(identifierX => item[identifierX])));
    const duplicatesByIdentifiers = duplicates.reduce((acc, duplicate) => {
        const duplicateIdentifier = identifiers.map(identifierX => duplicate[identifierX]).toString();

        return { ...acc, [duplicateIdentifier]: duplicate };
    }, {});

    const { insert, update } = items.reduce((acc, item) => {
        const itemIdentifier = identifiers.map(identifierX => item[identifierX]).toString();

        if (duplicatesByIdentifiers[itemIdentifier]) {
            acc.update.push(item);
            return acc;
        }

        acc.insert.push(item);
        return acc;
    }, {
        insert: [],
        update: [],
    });

    if (knex) {
        logger.debug(`${table}: Inserting ${insert.length}`);
        logger.debug(`${table}: Updating ${update.length}`);

        const [inserted, updated] = await Promise.all([
            knex(table).returning('*').insert(insert),
            knex.transaction(async trx => Promise.all(update.map((item) => {
                const clause = identifiers.reduce((acc, identifierX) => ({ ...acc, [identifierX]: item[identifierX] }), {});

                return trx
                    .where(clause)
                    .update(item)
                    .into(table)
                    .returning('*');
            }))),
        ]);

        return {
            inserted: Array.isArray(inserted) ? inserted : [],
            updated: updated.reduce((acc, updatedItems) => acc.concat(updatedItems), []),
        };
    }

    return { insert, update };
}

module.exports = upsert;
