# frozen_string_literal: true

class AddUniqueIndexOnPreviewCardsStatuses < ActiveRecord::Migration[6.1]
  disable_ddl_transaction!

  def up
    add_index :preview_cards_statuses, [:status_id, :preview_card_id], name: :preview_cards_statuses_pkey, algorithm: :concurrently, unique: true
  rescue ActiveRecord::RecordNotUnique
    deduplicate_and_reindex!
  end

  def down
    remove_index :preview_cards_statuses, name: :preview_cards_statuses_pkey
  end

  private

  def supports_concurrent_reindex?
    @supports_concurrent_reindex ||= begin
      version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
      version >= 120_000
    end
  end

  def deduplicate_and_reindex!
    deduplicate_preview_cards!

    if supports_concurrent_reindex?
      safety_assured { execute 'REINDEX INDEX CONCURRENTLY preview_cards_statuses_pkey' }
    else
      remove_index :preview_cards_statuses, name: :preview_cards_statuses_pkey
      add_index :preview_cards_statuses, [:status_id, :preview_card_id], name: :preview_cards_statuses_pkey, algorithm: :concurrently, unique: true
    end
  rescue ActiveRecord::RecordNotUnique
    retry
  end

  def deduplicate_preview_cards!
    # Statuses should have only one preview card at most, even if that's not the database
    # constraint we will end up with
    duplicate_ids = select_all('SELECT status_id FROM preview_cards_statuses GROUP BY status_id HAVING count(*) > 1;').rows

    duplicate_ids.each_slice(1000) do |ids|
      # This one is tricky: since we don't have primary keys to keep only one record,
      # use the physical `ctid`
      safety_assured do
        execute "DELETE FROM preview_cards_statuses p WHERE p.status_id IN (#{ids.join(', ')}) AND p.ctid NOT IN (SELECT q.ctid FROM preview_cards_statuses q WHERE q.status_id = p.status_id LIMIT 1)"
      end
    end
  end
end
