# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/

class SecureMailing::PGP::Incoming < SecureMailing::Backend::Handler
  attr_accessor :mail, :content_type

  EXPRESSION_ENCRYPTED = %r{application/pgp-encrypted}i.freeze
  EXPRESSION_SIGNATURE = %r{application/pgp-signature}i.freeze

  def initialize(mail)
    super()

    @mail = mail
    @content_type = mail[:mail_instance].content_type
  end

  def process
    return unless process?

    initialize_article_preferences
    decrypt
    verify_signature
    log
  end

  def initialize_article_preferences
    article_preferences[:security] = {
      type: 'PGP',
      sign: {
        success: false,
        comment: nil
      },
      encryption: {
        success: false,
        comment: nil
      }
    }
  end

  def article_preferences
    @article_preferences ||= begin
      key = :'x-zammad-article-preferences'
      mail[key] ||= {}
      mail[key]
    end
  end

  def process?
    signed? || encrypted?
  end

  def signed?(check_content_type = content_type)
    EXPRESSION_SIGNATURE.match?(check_content_type)
  end

  def encrypted?(check_content_type = content_type)
    EXPRESSION_ENCRYPTED.match?(check_content_type)
  end

  def decrypt
    return unless encrypted?

    success = false
    comment = 'Private key for decryption could not be found.'
    ::PGPKeypair.where.not(private_key: [nil, '']).find_each do |cert|
      begin
        index = mail[:attachments].index { |file| file[:preferences]['Content-Type'] == 'application/pgp-encrypted' }
        data = mail[:attachments][index + 1][:data]
        decrypted_data = Sequoia.decrypt_for(ciphertext: data.chop, recipient: cert.private_key,
                                             password: cert.private_key_secret)
      rescue StandardError
        next
      end

      parse_new_mail(decrypted_data)

      success = true
      comment = cert.email_addresses.join(', ')

      # overwrite content_type for signature checking
      @content_type = mail[:mail_instance].content_type
      break
    end

    article_preferences[:security][:encryption] = {
      success: success,
      comment: comment
    }
  end

  def verify_signature
    return unless signed?

    success = false
    comment = 'Certificate for verification could not be found.'

    ::PGPKeypair.where.not(public_key: [nil, '']).find_each do |cert|
      next unless cert.email_addresses.include? mail[:from_email]

      begin
        index = mail[:attachments].index { |file| file[:preferences]['Mime-Type'] == 'application/pgp-signature' }
        data = mail[:attachments][index][:data]
        verified_data = Sequoia.verify_detached_from(plaintext: mail[:mail_instance].body.encoded, signature: data.chop,
                                                     sender: cert.public_key)
      rescue StandardError
        next
      end

      parse_new_mail(verified_data)

      success = true
      comment = cert.email_addresses.join(', ')

      # overwrite content_type for signature checking
      @content_type = mail[:mail_instance].content_type
      break
    end

    article_preferences[:security][:sign] = {
      success: success,
      comment: comment
    }
  end

  private

  def log
    %i[sign encryption].each do |action|
      result = article_preferences[:security][action]
      next if result.blank?

      if result[:success]
        status = 'success'
      elsif result[:comment].blank?
        # means not performed
        next
      else
        status = 'failed'
      end

      HttpLog.create(
        direction: 'in',
        facility: 'PGP',
        url: "#{mail[:from_email]} -> #{mail[:to]}",
        status: status,
        ip: nil,
        request: {
          message_id: mail[:message_id]
        },
        response: article_preferences[:security],
        method: action,
        created_by_id: 1,
        updated_by_id: 1
      )
    end
  end

  def parse_new_mail(new_mail)
    mail[:mail_instance].header['Content-Type'] = nil
    mail[:mail_instance].header['Content-Disposition'] = nil
    mail[:mail_instance].header['Content-Transfer-Encoding'] = nil
    mail[:mail_instance].header['Content-Description'] = nil

    new_raw_mail = "#{mail[:mail_instance].header}#{new_mail}"

    mail_new = Channel::EmailParser.new.parse(new_raw_mail)
    mail_new.each do |local_key, local_value|
      mail[local_key] = local_value
    end
  end
end
