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

require 'net/imap'

class Channel::Driver::Imap < Channel::EmailParser

=begin

fetch emails from IMAP account

  instance = Channel::Driver::Imap.new
  result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for)

returns

  {
    result: 'ok',
    fetched: 123,
    notice: 'e. g. message about to big emails in mailbox',
  }

check if connect to IMAP account is possible, return count of mails in mailbox

  instance = Channel::Driver::Imap.new
  result = instance.fetch(params[:inbound][:options], channel, 'check')

returns

  {
    result: 'ok',
    content_messages: 123,
  }

verify IMAP account, check if search email is in there

  instance = Channel::Driver::Imap.new
  result = instance.fetch(params[:inbound][:options], channel, 'verify', subject_looking_for)

returns

  {
    result: 'ok', # 'verify not ok'
  }

=end

  def fetch (options, channel, check_type = '', verify_string = '')
    ssl  = true
    port = 993
    if options.key?(:ssl) && options[:ssl].to_s == 'false'
      ssl  = false
      port = 143
    end

    Rails.logger.info "fetching imap (#{options[:host]}/#{options[:user]} port=#{port},ssl=#{ssl})"

    # on check, reduce open_timeout to have faster probing
    timeout = 12
    if check_type == 'check'
      timeout = 6
    end

    Timeout.timeout(timeout) do
      @imap = Net::IMAP.new( options[:host], port, ssl, nil, false )
    end

    # try LOGIN, if not - try plain
    begin
      @imap.authenticate('LOGIN', options[:user], options[:password])
    rescue => e
      if e.to_s !~ /(unsupported\s(authenticate|authentication)\smechanism|not\ssupported)/i
        raise e
      end
      @imap.login(options[:user], options[:password])
    end

    # select folder
    if !options[:folder] || options[:folder].empty?
      @imap.select('INBOX')
    else
      @imap.select(options[:folder])
    end

    message_ids = @imap.search(['ALL'])

    # check mode only
    if check_type == 'check'
      Rails.logger.info 'check only mode, fetch no emails'
      content_max_check = 5
      content_messages  = 0

      # check messages
      message_ids.each do |message_id|

        message_meta = @imap.fetch(message_id, ['RFC822.HEADER'])[0].attr

        # check how many content messages we have, for notice used
        header = message_meta['RFC822.HEADER']
        if header && header !~ /x-zammad-ignore/i
          content_messages += 1
          break if content_max_check < content_messages
        end
      end
      if content_messages >= content_max_check
        content_messages = message_ids.count
      end
      disconnect
      return {
        result: 'ok',
        content_messages: content_messages,
      }
    end

    # reverse message order to increase performance
    if check_type == 'verify'
      Rails.logger.info "verify mode, fetch no emails #{verify_string}"
      message_ids.reverse!

      # check for verify message
      message_ids.each do |message_id|

        message_meta = @imap.fetch(message_id, ['ENVELOPE'])[0].attr

        # check if verify message exists
        subject = message_meta['ENVELOPE'].subject
        next if !subject
        next if subject !~ /#{verify_string}/
        Rails.logger.info " - verify email #{verify_string} found"
        @imap.store(message_id, '+FLAGS', [:Deleted])
        @imap.expunge()
        disconnect
        return {
          result: 'ok',
        }
      end

      disconnect
      return {
        result: 'verify not ok',
      }
    end

    # fetch regular messages
    count_all     = message_ids.count
    count         = 0
    count_fetched = 0
    notice        = ''
    message_ids.each do |message_id|
      count += 1
      Rails.logger.info " - message #{count}/#{count_all}"
      #Rails.logger.info msg.to_s

      message_meta = @imap.fetch(message_id, ['RFC822.SIZE', 'FLAGS', 'INTERNALDATE'])[0]

      # ignore to big messages
      max_message_size = Setting.get('postmaster_max_size').to_f
      real_message_size = message_meta.attr['RFC822.SIZE'].to_f / 1024 / 1024
      if real_message_size > max_message_size
        info = "  - ignore message #{count}/#{count_all} - because message is to big (is:#{real_message_size} MB/max:#{max_message_size} MB)"
        Rails.logger.info info
        notice += "#{info}\n"
        next
      end

      # ignore deleted messages
      if message_meta.attr['FLAGS'].include?(:Deleted)
        Rails.logger.info "  - ignore message #{count}/#{count_all} - because message has already delete flag"
        next
      end

      # delete email from server after article was created
      msg = @imap.fetch(message_id, 'RFC822')[0].attr['RFC822']
      next if !msg
      if process(channel, msg)
        @imap.store(message_id, '+FLAGS', [:Deleted])
        count_fetched += 1
      end
    end
    @imap.expunge()
    disconnect
    if count == 0
      Rails.logger.info ' - no message'
    end
    Rails.logger.info 'done'
    {
      result: 'ok',
      fetched: count_fetched,
      notice: notice,
    }
  end

  def disconnect
    return if !@imap
    @imap.disconnect()
  end
end
