require 'test_helper'
require 'net/imap'

class EmailPostmasterToSender < ActiveSupport::TestCase

  setup do
    Setting.set('postmaster_max_size', 0.1)

    @test_id = rand(999_999_999)

    # setup the IMAP account info for Zammad
    if ENV['MAIL_SERVER'].blank?
      raise "Need MAIL_SERVER as ENV variable like export MAIL_SERVER='mx.example.com'"
    end
    if ENV['MAIL_SERVER_ACCOUNT'].blank?
      raise "Need MAIL_SERVER_ACCOUNT as ENV variable like export MAIL_SERVER_ACCOUNT='user:somepass'"
    end

    @server_address = ENV['MAIL_SERVER']
    @server_login = ENV['MAIL_SERVER_ACCOUNT'].split(':')[0]
    @server_password = ENV['MAIL_SERVER_ACCOUNT'].split(':')[1]

    @folder = "postmaster_to_sender_#{@test_id}"

    if ENV['MAIL_SERVER_EMAIL'].blank?
      raise "Need MAIL_SERVER_EMAIL as ENV variable like export MAIL_SERVER_EMAIL='master@example.com'"
    end

    @sender_email_address = ENV['MAIL_SERVER_EMAIL']

    @email_address = EmailAddress.create!(
      realname:      'me Helpdesk',
      email:         "some-zammad-#{ENV['MAIL_SERVER_EMAIL']}",
      updated_by_id: 1,
      created_by_id: 1,
    )

    group = Group.create_or_update(
      name:             'PostmasterToSenderTest',
      email_address_id: @email_address.id,
      updated_by_id:    1,
      created_by_id:    1,
    )

    @channel = Channel.create!(
      area:          'Email::Account',
      group_id:      group.id,
      options:       {
        inbound:  {
          adapter: 'imap',
          options: {
            host:           @server_address,
            user:           @server_login,
            password:       @server_password,
            ssl:            true,
            folder:         @folder,
            keep_on_server: false,
          }
        },
        outbound: {
          adapter: 'smtp',
          options: {
            host:      @server_address,
            port:      25,
            start_tls: true,
            user:      @server_login,
            password:  @server_password,
            email:     @email_address.email
          },
        },
      },
      active:        true,
      updated_by_id: 1,
      created_by_id: 1,
    )
    @email_address.channel_id = @channel.id
    @email_address.save!
  end

  test 'postmaster reply with email on oversized incoming emails' do
    imap = Net::IMAP.new(@server_address, 993, true, nil, false)
    imap.login(@server_login, @server_password)
    imap.create(@folder)
    imap.select(@folder)

    # put a very large message in it
    large_message = "Subject: Oversized Email Message
From: Max Mustermann <#{@sender_email_address}>
To: shugo@example.com
Message-ID: <#{@test_id}@zammad.test.com>

Oversized Email Message Body #{'#' * 120_000}
".gsub(/\n/, "\r\n")

    large_message_md5 = Digest::MD5.hexdigest(large_message)
    large_message_size = format('%.2f', large_message.size.to_f / 1024 / 1024)

    imap.append(@folder, large_message, [], Time.zone.now)

    @channel.fetch(true)

    # 1. verify that the oversized email has been saved locally to:
    # /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
    path = Rails.root.join('tmp', 'oversized_mail')
    target_files = Dir.entries(path).select do |filename|
      filename =~ /^#{large_message_md5}\.eml$/
    end
    assert(target_files.present?, 'Large message .eml log file must be present.')

    # pick the latest file that matches the criteria
    target_file = target_files.max

    # verify that the file is byte for byte identical to the sent message
    file_path = Rails.root.join('tmp', 'oversized_mail', target_file)
    eml_data = File.read(file_path)
    assert_equal(large_message, eml_data)

    # 2. verify that a postmaster response email has been sent to the sender
    imap.select('inbox')
    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
    assert(message_ids.count.positive?, 'Must have received a reply from the postmaster')
    imap_message_id = message_ids.last
    msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
    assert(msg.present?, 'Must have received a reply from the postmaster')
    imap.store(imap_message_id, '+FLAGS', [:Deleted])
    imap.expunge()

    # parse the reply mail and verify the various headers
    parser = Channel::EmailParser.new
    mail = parser.parse(msg)
    assert_equal(mail[:from_email], @email_address.email)
    assert_equal(mail[:subject], '[undeliverable] Message too large')
    assert_equal("<#{@test_id}@zammad.test.com>",
                 mail['references'],
                 'Reply\'s Referecnes header must match the send message ID')
    assert_equal("<#{@test_id}@zammad.test.com>",
                 mail['in-reply-to'],
                 'Reply\'s In-Reply-To header must match the send message ID')

    # verify the reply mail body content
    body = mail[:body]
    assert(body.start_with?('Dear Max Mustermann'), 'Body must contain sender name')
    assert(body.include?('Oversized Email Message'), 'Body must contain original subject')
    assert(body.include?('0.1 MB'), 'Body must contain max allowed message size')
    assert(body.include?("#{large_message_size} MB"), 'Body must contain the original message size')
    assert(body.include?(Setting.get('fqdn')), 'Body must contain the Zammad instance name')

    # 3. check if original mail got removed
    imap.select(@folder)
    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
    assert_equal(message_ids.count, 0, 'Original customer mail must be deleted.')

    # final clean up
    imap.delete(@folder)
    @channel.destroy!
  end

  test 'postmaster reply with no email on oversized incoming emails' do
    Setting.set('postmaster_send_reject_if_mail_too_large', false)
    imap = Net::IMAP.new(@server_address, 993, true, nil, false)
    imap.login(@server_login, @server_password)

    imap.select('inbox')
    message_count = imap.sort(['DATE'], ['ALL'], 'US-ASCII').count

    imap.create(@folder)
    imap.select(@folder)

    # put a very large message in it
    large_message = "Subject: Oversized Email Message
From: Max Mustermann <#{@sender_email_address}>
To: shugo@example.com
Message-ID: <#{@test_id}@zammad.test.com>

Oversized Email Message Body #{'#' * 120_000}
".gsub(/\n/, "\r\n")

    imap.append(@folder, large_message, [], Time.zone.now)

    @channel.fetch(true)

    # 1. verify that the oversized email has been saved locally to:
    # /tmp/oversized_mail/yyyy-mm-ddThh:mm:ss-:md5.eml
    path = Rails.root.join('tmp', 'oversized_mail')
    target_files = Dir.entries(path).select do |filename|
      filename =~ /^.+?\.eml$/
    end
    assert_not(target_files.blank?, 'Large message .eml log file must be blank.')

    # 2. verify that a postmaster response email has been sent to the sender
    imap.select('inbox')
    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
    assert_equal(message_ids.count, message_count, 'Must not have received a reply from the postmaster')

    # 3. check if original mail got removed
    imap.select(@folder)
    message_ids = imap.sort(['DATE'], ['ALL'], 'US-ASCII')
    imap_message_id = message_ids.last
    msg = imap.fetch(imap_message_id, 'RFC822')[0].attr['RFC822']
    imap.store(imap_message_id, '+FLAGS', [:Deleted])
    imap.expunge()
    assert(msg.present?, 'Oversized Email Message')
    assert_equal(message_ids.count, 1, 'Original customer mail must be deleted.')

    # final clean up
    imap.delete(@folder)
    @channel.destroy!
  end

  teardown do
    Setting.set('postmaster_max_size', 10)
  end
end
