class Notification < ApplicationRecord
  include Purging

  belongs_to :notification_type
  belongs_to :initiator, :class_name => "User", :foreign_key => 'user_id'
  belongs_to :subject, :polymorphic => true
  belongs_to :cause, :polymorphic => true
  has_many :notification_recipients, :dependent => :delete_all
  has_many :recipients, :class_name => "User", :through => :notification_recipients, :source => :user

  accepts_nested_attributes_for :notification_recipients
  before_create :set_notification_recipients
  # Do not emit notifications if they are not enabled for the server
  after_commit :emit_message, :on => :create

  before_save :backup_subject_name

  serialize :options, :type => Hash
  default_value_for(:options) { {} }

  validate :complete_bindings

  scope :of_type, ->(notification_type) { joins(:notification_type).where(:notification_types => {:name => notification_type}) }

  def type=(typ)
    self.notification_type = NotificationType.find_by!(:name => typ)
  end

  def self.emit_for_event(event)
    return unless NotificationType.names.include?(event.event_type)

    type = NotificationType.find_by(:name => event.event_type)
    return unless type.enabled?

    Notification.create(:notification_type => type,
                        :options           => event.full_data,
                        :subject           => event.target)
  end

  def to_h
    {
      :level      => notification_type.level,
      :created_at => created_at,
      :text       => notification_type.message,
      :bindings   => text_bindings
    }
  end

  def seen_by_all_recipients?
    notification_recipients.unseen.empty?
  end

  def self.notification_text(name, message_params)
    return unless message_params && NotificationType.names.include?(name)

    type = NotificationType.find_by(:name => name)
    type.message % message_params
  end

  private

  def complete_bindings
    notification_type.message % to_h[:bindings]
  rescue ArgumentError, KeyError => e
    # 1. Deprecate now
    # 2. Fail validation going forward via errors.add(error_args)
    error_args = [:options, "text bindings for notification_type: '#{notification_type.name}' failed with error: '#{e.message}' with options: '#{options.inspect}' and message #{notification_type.message.inspect}. Next release will not allow a notification without complete bindings."]
    Vmdb::Deprecation.warn(error_args.join(' '), caller_locations[1..-1].reject { |location| location.label.include?("emit_for_event") })
  end

  def emit_message
    return unless ::Settings.server.asynchronous_notifications

    notification_recipients.pluck(:id, :user_id).each do |id, user|
      ActionCable.server.broadcast("notifications_#{user}", to_h.merge(:id => id.to_s))
    end
  end

  def set_notification_recipients
    subscribers = notification_type.subscriber_ids(subject, initiator)
    if subject
      subscribers.reject! do |subscriber_id|
        Rbac.filtered_object(subject, :user => User.find(subscriber_id)).blank?
      end
    end
    self.notification_recipients_attributes = subscribers.collect { |id| {:user_id => id} }
  end

  def backup_subject_name
    return unless subject

    backup_name = (subject.try(:name) || subject.try(:description))

    # Note, options are read in text_bindings_dynamic and used in text_bindings
    # if the subject is no longer there such as when a vm is deleted.
    options[:subject] = backup_name if backup_name
  end

  def text_bindings
    [:initiator, :subject, :cause].each_with_object(text_bindings_dynamic) do |key, result|
      value = public_send(key)
      next unless value

      # Set the link based on the notification_type.link_to
      result[:link] = {
        :id    => value.id,
        :model => value.class.name
      } if notification_type.link_to.try(:to_sym) == key

      result[key] = {
        :text => value.try(:name) || value.try(:description)
      }
    end
  end

  def text_bindings_dynamic
    options.each_with_object({}) do |(key, value), result|
      result[key] = {
        :text => value
      }
    end
  end
end
