# This file is a part of Redmine CRM (redmine_contacts) plugin,
# customer relationship management plugin for Redmine
#
# Copyright (C) 2010-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_contacts is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_contacts is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_contacts.  If not, see <http://www.gnu.org/licenses/>.

class ContactsMailer < ActionMailer::Base
  include Redmine::I18n

  class UnauthorizedAction < StandardError; end
  class MissingInformation < StandardError; end

  helper :application

  attr_reader :email, :user

  def self.default_url_options
    h = Setting.host_name
    h = h.to_s.gsub(%r{\/.*$}, '') unless Redmine::Utils.relative_url_root.blank?
    { :host => h, :protocol => Setting.protocol }
  end

  def bulk_mail(contact, params = {})
    raise l(:error_empty_email) if (contact.emails.empty? || params[:message].blank?)

    @contact = contact
    @params = params

    params[:attachments].each_value do |mail_attachment|
      if file = mail_attachment['file']
        file.rewind if file
        attachments[file.original_filename] = file.binread
        file.rewind if file
      elsif token = mail_attachment['token']
        if token.to_s =~ /^(\d+)\.([0-9a-f]+)$/
          attachment_id, attachment_digest = $1, $2
          if a = Attachment.where(:id => attachment_id, :digest => attachment_digest).first
            attachments[a.filename] = File.binread(a.diskfile)
          end
        end
      end
    end unless params[:attachments].blank?

    mail(:from => params[:from] || User.current.mail,
         :to => contact.emails.first,
         :cc => params[:cc],
         :bcc => params[:bcc],
         :subject => params[:subject]) do |format|
       format.text
       format.html
    end

  end

  def self.receive(email, options={})
    @@contacts_mailer_options = options.dup
    super email
  end

  # Processes incoming emails
  # Returns the created object (eg. an issue, a message) or false
  def receive(email)
    # debugger
    @email = email
    sender_email = email.from.to_a.first.to_s.strip
    # Ignore emails received from the application emission address to avoid hell cycles
    if sender_email.downcase == Setting.mail_from.to_s.strip.downcase
      logger.info  "ContactsMailHandler: ignoring email from Redmine emission address [#{sender_email}]" if logger && logger.info
      return false
    end
    @user = User.find_by_mail(sender_email) if sender_email.present?
    if @user.nil? || (@user && !@user.active?)
      logger.info   "ContactsMailHandler: user not found [#{sender_email}]" if logger && logger.info
    end
    dispatch
  end

  def dispatch
    deal_id = email.to.to_s.match(/.+\+d([0-9]*)/).to_a[1]
    deal_id ||= email.bcc.to_s.match(/.+\+d([0-9]*)/).to_a[1]
    deal_id ||= email.cc.to_s.match(/.+\+d([0-9]*)/).to_a[1]

    if deal_id
      deal = Deal.find_by_id(deal_id)
      if deal
        return [*receive_deal_note(deal_id)]
      end
    end

    contacts = []

    if contacts.blank?
      contact_id = email.to.to_s.match(/.+\+c([0-9]*)/).to_a[1]
      contact_id ||= email.bcc.to_s.match(/.+\+c([0-9]*)/).to_a[1]
      contact_id ||= email.cc.to_s.match(/.+\+c([0-9]*)/).to_a[1]
      contacts = Contact.where(:id => contact_id)
    end

    if contacts.blank?
      contacts = Contact.find_by_emails(email.to.to_a)
    end

    if contacts.blank?
      from_key_words = get_keyword_locales(:label_crm_mail_from)
      @plain_text_body = plain_text_body.gsub(/^>\s*/, '').gsub('&gt; ','').gsub('&quot;', '"')
      full_address = plain_text_body.match(/^(#{from_key_words.join('|')})[ \s]*:[ \s]*(.+)\s*$/).to_a[2]

      email_address = full_address.match(/[\w,\.,\-,\+]+@.+\.\w{2,}/) if full_address
      contacts = Contact.find_by_emails([email_address.to_s.strip]) if email_address
    end

    if contacts.blank?
      return false
    end

    raise MissingInformation if contacts.blank?

    result = []
    contacts.each do |contact|
      result << receive_contact_note(contact.id)
    end
    result

  rescue ActiveRecord::RecordInvalid => e
    # TODO: send a email to the user
    logger.error e.message if logger
    false
  rescue MissingInformation => e
    logger.error "ContactsMailHandler: missing information from #{user}: #{e.message}" if logger
    false
  rescue UnauthorizedAction => e
    logger.error "ContactsMailHandler: unauthorized attempt from #{user}" if logger
    false
  end

  # Receives a reply to a forum message
  def receive_contact_note(contact_id)
    contact = Contact.find_by_id(contact_id)
    note = nil
    # logger.error "ContactsMailHandler: receive_contact_note user: #{user},
    #               contact: #{contact.name},
    #               editable: #{contact.editable?(self.user)},
    #               current: #{User.current}"
    raise UnauthorizedAction unless contact.editable?(self.user)
    if contact
        note = ContactNote.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
                        :type_id => Note.note_types[:email],
                        :content => plain_text_body,
                        :created_on => email.date)
        note.author = self.user
        contact.notes << note
        add_attachments(note)
        logger.info note
        note.save
        contact.save
    end
    note
  end

  def receive_deal_note(deal_id)
    deal = Deal.find_by_id(deal_id)
    note = nil
    # logger.error "ContactsMailHandler: receive_contact_note user: #{user},
    #               contact: #{contact.name},
    #               editable: #{contact.editable?(self.user)},
    #               current: #{User.current}"
    raise UnauthorizedAction unless deal.editable?(self.user)
    if deal
        note = DealNote.new(:subject => email.subject.gsub(%r{^.*msg\d+\]}, '').strip,
                        :type_id => Note.note_types[:email],
                        :content => plain_text_body,
                        :created_on => email.date)
        note.author = self.user
        deal.notes << note
        add_attachments(note)
        logger.info note
        note.save
        deal.save
    end
    note
  end

  private

  # Destructively extracts the value for +attr+ in +text+
  # Returns nil if no matching keyword found
  def extract_keyword!(text, attr, format=nil)
    keys = [attr.to_s.humanize]
    if attr.is_a?(Symbol)
      keys << l("field_#{attr}", :default => '', :locale =>  user.language) if user && user.language.present?
      keys << l("field_#{attr}", :default => '', :locale =>  Setting.default_language) if Setting.default_language.present?
    end
    keys.reject! {|k| k.blank?}
    keys.collect! {|k| Regexp.escape(k)}
    format ||= '.+'
    text.gsub!(/^(#{keys.join('|')})[ \t]*:[ \t]*(#{format})\s*$/i, '') # /^(От:)[ \t]*:[ \t]*(.+)\s*$/i
    $2 && $2.strip
  end

  def add_attachments(obj)
    if email.attachments && email.attachments.any?
      email.attachments.each do |attachment|
        obj.attachments << Attachment.create(:container => obj,
                          :file => attachment.decoded,
                          :filename => attachment.filename,
                          :author => user,
                          :content_type => attachment.mime_type)
      end
    end
  end

  # Returns the text/plain part of the email
  # If not found (eg. HTML-only email), returns the body with tags removed
  def plain_text_body

    return @plain_text_body unless @plain_text_body.nil?

    part = email.text_part || email.html_part || email
    @plain_text_body = Redmine::CodesetUtil.to_utf8(part.body.decoded, part.charset)

    # strip html tags and remove doctype directive
    @plain_text_body = ActionController::Base.helpers.strip_tags(@plain_text_body.strip) unless email.text_part
    @plain_text_body.sub! %r{^<!DOCTYPE .*$}, ''
    @plain_text_body

  end

  def get_keyword_locales(keyword)
    I18n.available_locales.collect{|lc| l(keyword, :locale => lc)}.uniq
  end

  # Appends a Redmine header field (name is prepended with 'X-Redmine-')
  def redmine_headers(h)
    h.each { |k,v| headers["X-Redmine-#{k}"] = v }
  end

  def initialize_defaults(method_name)
    super
    # Common headers
    headers 'X-Mailer' => 'Redmine Contacts',
            'X-Redmine-Host' => Setting.host_name,
            'X-Redmine-Site' => Setting.app_title
  end

  def logger
    Rails.logger
  end

end
