'use strict';

const isMissingTableError = (err) => {
  const message = String(err && err.message);
  return message.includes('No description found')
    || message.includes('does not exist')
    || message.includes('Cannot read properties of undefined');
};

const metadataColumns = (Sequelize) => ({
  sumber: {
    type: Sequelize.DataTypes.STRING(100),
  },
  external_id: {
    type: Sequelize.DataTypes.STRING(255),
  },
  external_type: {
    type: Sequelize.DataTypes.STRING(50),
  },
  raw_source_data: {
    type: Sequelize.DataTypes.JSONB,
  },
  last_synced_at: {
    type: Sequelize.DataTypes.DATE,
  },
});

module.exports = {
  async up(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();

    try {
      let table;
      try {
        table = await queryInterface.describeTable('places');
      } catch (error) {
        if (!isMissingTableError(error)) {
          throw error;
        }
        await transaction.commit();
        return;
      }

      const columns = metadataColumns(Sequelize);
      for (const [columnName, definition] of Object.entries(columns)) {
        if (!table[columnName]) {
          await queryInterface.addColumn('places', columnName, definition, { transaction });
        }
      }

      const indexes = await queryInterface.showIndex('places');
      const hasIndex = (name) => indexes.some((index) => index.name === name);

      if (!hasIndex('places_source_external_unique_idx')) {
        await queryInterface.addIndex('places', ['sumber', 'external_type', 'external_id'], {
          name: 'places_source_external_unique_idx',
          unique: true,
          where: {
            deletedAt: null,
          },
          transaction,
        });
      }

      if (!hasIndex('places_sumber_idx')) {
        await queryInterface.addIndex('places', ['sumber'], {
          name: 'places_sumber_idx',
          transaction,
        });
      }

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  },

  async down(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();

    try {
      let table;
      try {
        table = await queryInterface.describeTable('places');
      } catch (error) {
        if (!isMissingTableError(error)) {
          throw error;
        }
        await transaction.commit();
        return;
      }

      const indexes = await queryInterface.showIndex('places');
      const removeIndexIfExists = async (name) => {
        if (indexes.some((index) => index.name === name)) {
          await queryInterface.removeIndex('places', name, { transaction });
        }
      };

      await removeIndexIfExists('places_sumber_idx');
      await removeIndexIfExists('places_source_external_unique_idx');

      for (const columnName of Object.keys(metadataColumns(Sequelize)).reverse()) {
        if (table[columnName]) {
          await queryInterface.removeColumn('places', columnName, { transaction });
        }
      }

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  },
};
