#!/github/bin/safe-ruby

# USAGE
# save the contents of this script in /data/user/common/column_encryption_diagnostic.rb
#
# sudo chown git:git /data/user/common/column_encryption_diagnostic.rb
# sudo chmod u+x /data/user/common/column_encryption_diagnostic.rb
# github-env /data/user/common/column_encryption_diagnostic.rb | tee output.log
#
# After running the script, log files can be found in these locations:
# - Undecryptable records: /tmp/column_encryption_records_to_be_deleted.log
# - Users impacted by undecryptable 2FA records: /tmp/column_encryption_users_to_have_2fa_disabled.log
# - Unexpected errors: /tmp/column_encryption_unexpected_errors.log

require_relative "/github/config/environment"
require 'github/transitions/encrypt_column_with_current_key'

Failbot.disable # suppress failbot reporting of unhandled exceptions

puts 'Begin encrypted record diagnostics...';
puts 'This will take some time.'
puts

unexpected_error_output = File.open('/tmp/column_encryption_unexpected_errors.log', 'w')
records_to_be_deleted = File.open('/tmp/column_encryption_records_to_be_deleted.log', 'w')
users_to_have_2fa_disabled = File.open('/tmp/column_encryption_users_to_have_2fa_disabled.log', 'w')

available_keys = GitHub.encrypted_column_keying_material.split(';')
  .map { |k| Base64.strict_decode64(k) }
  .map { |k| ActiveRecord::Encryption::Key.new(k).id }
  .map(&:to_s)
  .join(', ');

puts 'Available decryption key IDs:'
puts available_keys
puts 'Processing records...'
errors = []
unexpected_errors = []
missing_key_records = []
missing_keys = []
repairable_encrypted_models = [TwoFactorCredential, User, TotpAppRegistration]
GitHub::Transitions::EncryptColumnWithCurrentKey::ENCRYPTED_MODELS.each do |model|
  if model.encrypted_attributes.present? && model.count > 0
    attrs = model.encrypted_attributes.to_a.join(', ')
    model.encrypted_attributes.each do |attribute|
      model.all.each do |record|
        count = 0
        begin
          next unless record.encrypted_attribute?(attribute)
          next if (record.instance_of?(TwoFactorCredential) || record.instance_of?(TotpAppRegistration)) && record.user.nil?
          count += 1
          ciphertext = record.ciphertext_for(attribute)
          next unless ciphertext.present?
          key_id = Base64.strict_decode64(
              ActiveSupport::JSON.decode(ciphertext)['h']['i']
          )
          unless available_keys.include?(key_id.to_s)
            if repairable_encrypted_models.include?(model)
              msg = "WARN: Missing key #{key_id.to_s} for #{model.to_s} attribute #{attribute.to_s} record id #{record.id.to_s}"
              puts msg
              missing_key_records << record
              missing_keys << key_id
              records_to_be_deleted.puts(msg)
              errors << msg
            else
              msg = "ERROR: Missing key #{key_id.to_s} for #{model.to_s} attribute #{attribute.to_s} record id #{record.id.to_s}"
              puts msg
              unexpected_error_output.puts(msg)
              unexpected_errors << msg
            end
          end
        rescue => e
          msg = "ERROR: Unexpected error processing #{model.to_s} attribute #{attribute.to_s} record id #{record.id.to_s}: #{e.message}"
          puts msg
          unexpected_error_output.puts(msg)
          unexpected_errors << msg
        end
        puts "#{model.to_s}: #{count.to_s} records with encrypted attribute: #{attribute.to_s}" if count > 0
      end
    end
  end
end
if unexpected_errors.any?
  puts "ERROR: Unexpected errors reading encrypted records! Log file: /tmp/column_encryption_unexpected_errors.log"
else
  if errors.any?
    puts 'WARN: Error reading encrypted records!'
    missing_keys_list = missing_keys.uniq.join(', ')
    puts 'WARN: Missing decryption key IDs: ' + missing_keys_list.to_s if missing_keys.any?
    puts 'WARN: Records with missing keys will be deleted when upgrading to 3.13 onwards.'
    two_factor_records = []
    two_factor_records = missing_key_records.filter do |record|
      record.instance_of?(TwoFactorCredential) || record.instance_of?(TotpAppRegistration)
    end
    users_who_need_to_reconfigure_2fa = two_factor_records.map(&:user).compact.map(&:login).uniq
    if users_who_need_to_reconfigure_2fa.any?
      msg = "WARN: The following users have invalid 2FA configuration records: \n#{users_who_need_to_reconfigure_2fa.join(', ')}"
      puts msg
      users_to_have_2fa_disabled.puts(msg)
    end
  else
    puts 'SUCCESS: Encrypted records OK.'
  end
end

unexpected_error_output.flush
unexpected_error_output.close
records_to_be_deleted.flush
records_to_be_deleted.close
users_to_have_2fa_disabled.flush
users_to_have_2fa_disabled.close

puts "Done."