'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 requestedColumns = (Sequelize) => ({
  nama: {
    type: Sequelize.DataTypes.STRING(255),
  },
  kategori: {
    type: Sequelize.DataTypes.STRING(100),
  },
  subkategori: {
    type: Sequelize.DataTypes.STRING(100),
  },
  alamat: {
    type: Sequelize.DataTypes.TEXT,
  },
  kelurahan: {
    type: Sequelize.DataTypes.STRING(100),
  },
  kecamatan: {
    type: Sequelize.DataTypes.STRING(100),
  },
  kota: {
    type: Sequelize.DataTypes.STRING(100),
  },
  provinsi: {
    type: Sequelize.DataTypes.STRING(100),
  },
  kode_pos: {
    type: Sequelize.DataTypes.STRING(20),
  },
  telepon: {
    type: Sequelize.DataTypes.STRING(100),
  },
  whatsapp: {
    type: Sequelize.DataTypes.STRING(100),
  },
  website: {
    type: Sequelize.DataTypes.STRING(255),
  },
  jam_buka: {
    type: Sequelize.DataTypes.STRING(255),
  },
  rating: {
    type: Sequelize.DataTypes.DECIMAL(2, 1),
    defaultValue: 0,
  },
  review_count: {
    type: Sequelize.DataTypes.INTEGER,
    defaultValue: 0,
  },
  verified: {
    type: Sequelize.DataTypes.BOOLEAN,
    defaultValue: false,
  },
  featured: {
    type: Sequelize.DataTypes.BOOLEAN,
    defaultValue: false,
  },
  popularitas: {
    type: Sequelize.DataTypes.INTEGER,
    defaultValue: 0,
  },
  created_at: {
    type: Sequelize.DataTypes.DATE,
    defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
  },
  updated_at: {
    type: Sequelize.DataTypes.DATE,
    defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
  },
});

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 = requestedColumns(Sequelize);
      for (const [columnName, definition] of Object.entries(columns)) {
        if (!table[columnName]) {
          await queryInterface.addColumn('places', columnName, definition, { transaction });
        }
      }

      if (table.latitude) {
        await queryInterface.sequelize.query(
          'ALTER TABLE "places" ALTER COLUMN "latitude" TYPE DOUBLE PRECISION USING "latitude"::double precision;',
          { transaction },
        );
      }

      if (table.longitude) {
        await queryInterface.sequelize.query(
          'ALTER TABLE "places" ALTER COLUMN "longitude" TYPE DOUBLE PRECISION USING "longitude"::double precision;',
          { transaction },
        );
      }

      if (table.status) {
        await queryInterface.sequelize.query(
          `ALTER TABLE "places"
             ALTER COLUMN "status" DROP DEFAULT,
             ALTER COLUMN "status" TYPE VARCHAR(50) USING "status"::text,
             ALTER COLUMN "status" SET DEFAULT 'aktif';`,
          { transaction },
        );
      } else {
        await queryInterface.addColumn(
          'places',
          'status',
          {
            type: Sequelize.DataTypes.STRING(50),
            defaultValue: 'aktif',
          },
          { transaction },
        );
      }

      await queryInterface.sequelize.query(
        `UPDATE "places"
           SET
             nama = COALESCE(nama, name),
             alamat = COALESCE(alamat, address),
             kota = COALESCE(kota, city),
             provinsi = COALESCE(provinsi, province),
             kode_pos = COALESCE(kode_pos, postal_code),
             telepon = COALESCE(telepon, phone_number),
             whatsapp = COALESCE(whatsapp, whatsapp_number),
             website = COALESCE(website, website_url),
             rating = COALESCE(rating, ROUND(LEAST(GREATEST(COALESCE(rating_average, 0), 0), 9.9)::numeric, 1), 0),
             review_count = COALESCE(review_count, rating_count, 0),
             verified = COALESCE(verified, is_verified, false),
             updated_at = COALESCE(updated_at, "updatedAt", NOW()),
             created_at = COALESCE(created_at, "createdAt", NOW())
         WHERE "deletedAt" IS NULL;`,
        { transaction },
      );

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

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

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

      if (!hasIndex('places_popularitas_idx')) {
        await queryInterface.addIndex('places', ['popularitas'], {
          name: 'places_popularitas_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_popularitas_idx');
      await removeIndexIfExists('places_status_featured_idx');
      await removeIndexIfExists('places_kota_kategori_idx');

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

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