# 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 Deal < ActiveRecord::Base
  unloadable
  include Redmine::SafeAttributes

  belongs_to :project
  belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
  belongs_to :assigned_to, :class_name => 'User', :foreign_key => 'assigned_to_id'
  belongs_to :category, :class_name => 'DealCategory', :foreign_key => 'category_id'
  belongs_to :contact
  belongs_to :status, :class_name => 'DealStatus', :foreign_key => 'status_id'
  has_many :deals, :class_name => 'Deal', :foreign_key => 'reference_id'
  has_many :notes, :as => :source, :class_name => 'DealNote', :dependent => :delete_all
  has_many :deal_processes, :dependent => :delete_all
  has_many :deals_issues, :dependent => :destroy
  has_many :issues, :through => :deals_issues

  if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
    has_many :lines, :class_name => 'ProductLine', :as => :container, :dependent => :delete_all
    has_many :products, :through => :lines, :uniq => true, :select => "#{Product.table_name}.*, #{ProductLine.table_name}.position"

    accepts_nested_attributes_for :lines, :allow_destroy => true
    safe_attributes 'lines_attributes'
    acts_as_priceable :amount, :tax_amount, :subtotal, :total

    before_validation :assign_lines
    before_save :calculate_price
  end

  if ActiveRecord::VERSION::MAJOR >= 4
    has_and_belongs_to_many :related_contacts, lambda { order("#{Contact.table_name}.last_name, #{Contact.table_name}.first_name") }, :uniq => true, :class_name => 'Contact'
  else
    has_and_belongs_to_many :related_contacts, :order => "#{Contact.table_name}.last_name, #{Contact.table_name}.first_name", :uniq => true, :class_name => 'Contact'
  end

  scope :visible, lambda {|*args|
    joins(:project).where(Project.allowed_to_condition(args.shift || User.current, :view_deals, *args))
  }
  scope :by_project, lambda { |project_id| where(:project_id => project_id) unless project_id.blank? }
  scope :deletable, lambda { |*args| joins(:project).where(Project.allowed_to_condition(args.first || User.current, :delete_deals)) }

  scope :live_search, lambda { |search| where("(#{Deal.table_name}.name LIKE ?)", "%#{search}%") }
  scope :live_search_with_contact, ->(search) do
    conditions = []
    values = {}
    search.split(' ').each_with_index { |word, index|
      key = :"v#{index}"
      conditions << "LOWER(#{Deal.table_name}.name) LIKE LOWER(:#{key})"
      conditions << "LOWER(#{Contact.table_name}.first_name) LIKE LOWER(:#{key})"
      conditions << "LOWER(#{Contact.table_name}.last_name) LIKE LOWER(:#{key})"
      conditions << "LOWER(#{Contact.table_name}.company) LIKE LOWER(:#{key})"
      conditions << "LOWER(#{Contact.table_name}.email) LIKE LOWER(:#{key})"
      values[key] = "%#{word}%"
    }
    sql = conditions.join(' OR ')
    joins(:contact).where(sql, values)
  end

  scope :open, lambda { joins(:status).where("(#{DealStatus.table_name}.status_type = ? OR #{DealStatus.table_name}.status_type IS NULL)", DealStatus::OPEN_STATUS) }
  scope :closed, lambda { joins(:status).where("#{DealStatus.table_name}.status_type <> ?", DealStatus::OPEN_STATUS) }
  scope :won, lambda { joins(:status).where("#{DealStatus.table_name}.status_type = ?", DealStatus::WON_STATUS) }
  scope :lost, lambda { joins(:status).where("#{DealStatus.table_name}.status_type = ?", DealStatus::LOST_STATUS) }
  scope :was_in_status, lambda { |status_id| joins(:deal_processes).where(["#{DealProcess.table_name}.old_value = ? OR #{DealProcess.table_name}.value = ?", status_id, status_id]).uniq }
  scope :with_status, lambda { |status_id| where(:status_id => status_id) }

  acts_as_priceable :price, :expected_revenue
  acts_as_customizable
  acts_as_viewable
  acts_as_watchable
  acts_as_attachable :view_permission => :view_deals,
                     :delete_permission => :edit_deals

  acts_as_event :datetime => :created_on,
                :url => Proc.new { |o| { :controller => 'deals', :action => 'show', :id => o } },
                :type => 'icon icon-add-deal',
                :title => Proc.new { |o| o.name },
                :description => Proc.new { |o| [o.price_to_s, o.contact ? o.contact.name : nil, o.background].join(' ').strip }

  if ActiveRecord::VERSION::MAJOR >= 4
    acts_as_activity_provider :type => 'deals',
                              :permission => :view_deals,
                              :author_key => :author_id,
                              :scope => joins(:project)

    acts_as_searchable :columns => ["#{table_name}.name",
                                    "#{table_name}.background",
                                    "#{DealNote.table_name}.content"],
                       :scope => includes([:project, :notes]),
                       :date_column => :created_on
  else
    acts_as_activity_provider :type => 'deals',
                              :permission => :view_deals,
                              :author_key => :author_id,
                              :find_options => { :include => :project }

    acts_as_searchable :columns => ["#{table_name}.name",
                                    "#{table_name}.background",
                                    "#{DealNote.table_name}.content"],
                       :include => [:project, :notes],
                       :order_column => "#{table_name}.id"
  end

  validates_presence_of :name, :project, :status
  validates_numericality_of :price, :allow_nil => true

  after_update :create_deal_process
  after_create :send_notification

  attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
  safe_attributes 'name',
                  'background',
                  'currency',
                  'price',
                  'price_type',
                  'duration',
                  'project_id',
                  'author_id',
                  'assigned_to_id',
                  'status_id',
                  'contact_id',
                  'category_id',
                  'probability',
                  'due_date',
                  'custom_field_values',
                  'custom_fields',
                  'watcher_user_ids',
                  :if => lambda { |deal, user| deal.new_record? || user.allowed_to?(:edit_deals, deal.project) }

  def initialize(attributes = nil, *args)
    super
    return unless new_record?
    # set default values for new records only
    self.status_id = DealStatus.default.try(:id)
    self.currency ||= ContactsSetting.default_currency
  end

  def avatar
  end

  def expected_revenue
    probability ? (probability.to_f / 100) * price.to_f : price
  end

  def full_name
    result = ''
    result << contact.name + ': ' unless contact.blank?
    result << name
  end

  def all_contacts
    @all_contacts ||= ([contact] + related_contacts).uniq
  end

  def self.available_users(prj = nil)
    cond = '(1=1)'
    cond << " AND #{Deal.table_name}.project_id = #{prj.id}" if prj
    User.active.select("DISTINCT #{User.table_name}.*").
                joins("JOIN #{Deal.table_name} ON #{Deal.table_name}.assigned_to_id = #{User.table_name}.id").
                where(cond).
                order("#{User.table_name}.lastname, #{User.table_name}.firstname")
  end

  def open?
    status.blank? || status.is_open?
  end

  def init_deal_process(author)
    @current_deal_process ||= DealProcess.new(:deal => self, :author => (author || User.current))
    @deal_status_before_change = new_record? ? nil : status_id
    @current_deal_process
  end

  def create_deal_process
    if @current_deal_process && @deal_status_before_change && !(@deal_status_before_change == status_id)
      @current_deal_process.old_value = @deal_status_before_change
      @current_deal_process.value = status_id
      @current_deal_process.save
      init_deal_process @current_deal_process.author
    end
  end

  def visible?(usr = nil)
    (usr || User.current).allowed_to?(:view_deals, project)
  end

  def editable?(usr = nil)
    (usr || User.current).allowed_to?(:edit_deals, project)
  end

  def destroyable?(usr = nil)
    (usr || User.current).allowed_to?(:delete_deals, project)
  end

  # Returns an array of projects that user can move deal to
  def self.allowed_target_projects(user = User.current)
    Project.where(Project.allowed_to_condition(user, :add_deals))
  end

  # Returns the mail adresses of users that should be notified
  def recipients
    notified = []
    # Author and assignee are always notified unless they have been
    # locked or don't want to be notified
    notified << author if author
    if assigned_to
      notified += (assigned_to.is_a?(Group) ? assigned_to.users : [assigned_to])
    end

    notified += project.notified_users
    notified = notified.select { |u| u.active? }
    notified.uniq!
    # Remove users that can not view the contact
    notified.reject! { |user| !visible?(user) }
    notified.collect(&:mail)
  end

  def status_was
    if status_id_changed? && status_id_was.present?
      @status_was ||= DealStatus.find_by_id(status_id_was)
    end
  end

  def copy_from(arg)
    deal = arg.is_a?(Deal) ? arg : Deal.visible.find(arg)
    self.attributes = deal.attributes.dup.except('id', 'created_at', 'updated_at')
    self.custom_field_values = deal.custom_field_values.inject({}) { |h, v| h[v.custom_field_id] = v.value ; h }
    if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
      deal.lines.each do |line|
        lines.build(line.attributes)
      end
    end
    self
  end

  def contact_country
    try(:contact).try(:address).try(:country)
  end

  def contact_city
    try(:contact).try(:address).try(:city)
  end

  if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
    def has_taxes?
      !lines.map(&:tax).all? { |t| t == 0 || t.blank? }
    end

    def has_discounts?
      !lines.map(&:discount).all? { |t| t == 0 || t.blank? }
    end

    def tax_amount
      lines.select { |l| !l.marked_for_destruction? }.inject(0) { |sum, l| sum + l.tax_amount }.to_f
    end

    def subtotal
      lines.select { |l| !l.marked_for_destruction? }.inject(0) { |sum, l| sum + l.total }.to_f
    end

    def total_units
      lines.inject(0) { |sum, l| sum + (l.product.blank? ? 0 : l.quantity) }
    end

    def calculate_price
      return true if lines.select { |l| !l.marked_for_destruction? }.empty?
      self.price = subtotal + (ContactsSetting.tax_exclusive? ? tax_amount : 0)
    end
  end

  def info
    result = ''
    result = status.name if status
    result = result + ' - ' + price_to_s unless price.blank?
    result.html_safe
  end
  private

  def send_notification
    Mailer.crm_deal_add(self).deliver if Setting.notified_events.include?('crm_deal_added')
  end

  if Redmine::Plugin.load && Redmine::Plugin.installed?(:redmine_products) && Redmine::Plugin.find(:redmine_products).version >= '2.0.2'
    def assign_lines
      return unless new_record?
      lines.each { |l| l.container = self }
    end
  end
end
