# frozen_string_literal: true

# migrate the invoices from being attached to a user to invoicing_profiles which are GDPR compliant
class MigrateInvoiceToInvoicingProfile < ActiveRecord::Migration
  def up
    # first, check the footprints
    check_footprints

    # if everything is ok, proceed with migration
    # remove and save periods in memory
    periods = backup_and_remove_periods
    # migrate invoices
    puts 'Migrating invoices. This may take a while...'
    Invoice.order(:id).all.each do |i|
      user = User.find(i.user_id)
      operator = User.find_by(id: i.operator_id)
      i.update_column('invoicing_profile_id', user.invoicing_profile.id)
      i.update_column('statistic_profile_id', user.statistic_profile.id)
      i.update_column('operator_profile_id', operator&.invoicing_profile&.id)
      i.update_column('user_id', nil)
      i.update_column('operator_id', nil)
    end
    # chain all records
    InvoiceItem.order(:id).all.each(&:chain_record)
    Invoice.order(:id).all.each(&:chain_record)
    # write memory dump into database
    restore_periods(periods)
  end

  def down
    # here we don't check footprints to save processing time and because this is pointless when reverting the migrations

    # remove and save periods in memory
    periods = backup_and_remove_periods
    # reset invoices
    Invoice.order(:created_at).all.each do |i|
      i.update_column('user_id', i.invoicing_profile.user_id)
      i.update_column('operator_id', i.operator_profile.user_id)
      i.update_column('invoicing_profile_id', nil)
      i.update_column('statistic_profile_id', nil)
      i.update_column('operator_profile_id', nil)
    end
    # chain all records
    InvoiceItem.order(:id).all.each(&:chain_record)
    Invoice.order(:id).all.each(&:chain_record)
    # write memory dump into database
    restore_periods(periods)
  end

  def check_footprints
    if AccountingPeriod.count.positive?
      last_period = AccountingPeriod.order(start_at: 'DESC').first
      puts "Checking invoices footprints from #{last_period.end_at}. This may take a while..."
      Invoice.where('created_at > ?', last_period.end_at).order(:id).each do |i|
        raise "Invalid footprint for invoice #{i.id}" unless i.check_footprint
      end
    else
      puts 'Checking all invoices footprints. This may take a while...'
      Invoice.order(:id).all.each do |i|
        raise "Invalid footprint for invoice #{i.id}" unless i.check_footprint
      end
    end
  end

  # will return an array of hash containing the removed periods data
  def backup_and_remove_periods
    return [] unless AccountingPeriod.count.positive?

    puts 'Removing accounting archives...'
    # 1. remove protection for AccountingPeriods
    execute("DROP RULE IF EXISTS accounting_periods_del_protect ON #{AccountingPeriod.arel_table.name};")
    # 2. backup AccountingPeriods in memory
    periods = []
    AccountingPeriod.all.each do |p|
      periods.push(
        start_at: p.start_at,
        end_at: p.end_at,
        closed_at: p.closed_at,
        closed_by: p.closed_by
      )
    end
    # 3. Delete periods from database
    AccountingPeriod.all.each do |ap|
      execute("DELETE FROM accounting_periods WHERE ID=#{ap.id};")
    end
    periods
  end

  def restore_periods(periods)
    return unless periods.size.positive?

    # 1. recreate AccountingPeriods
    puts 'Recreating accounting archives. This may take a while...'
    periods.each do |p|
      AccountingPeriod.create!(
        start_at: p[:start_at],
        end_at: p[:end_at],
        closed_at: p[:closed_at],
        closed_by: p[:closed_by]
      )
    end
    # 2. reset protection for AccountingPeriods
    execute("CREATE RULE accounting_periods_del_protect AS ON DELETE TO #{AccountingPeriod.arel_table.name} DO INSTEAD NOTHING;")
  end
end