require_dependency 'enum'

class Notification < ActiveRecord::Base
  belongs_to :user
  belongs_to :topic

  validates_presence_of :data
  validates_presence_of :notification_type

  scope :unread, lambda { where(read: false) }
  scope :recent, lambda {|n=nil| n ||= 10; order('created_at desc').limit(n) }

  after_save :refresh_notification_count
  after_destroy :refresh_notification_count

  def self.ensure_consistency!
    Notification.exec_sql("
    DELETE FROM Notifications n WHERE notification_type = :id AND
    NOT EXISTS(
      SELECT 1 FROM posts p
      JOIN topics t ON t.id = p.topic_id
      WHERE p.deleted_at is null AND t.deleted_at IS NULL
        AND p.post_number = n.post_number AND t.id = n.topic_id
    )" , id: Notification.types[:private_message])
  end

  def self.types
    @types ||= Enum.new(
      :mentioned, :replied, :quoted, :edited, :liked, :private_message,
      :invited_to_private_message, :invitee_accepted, :posted, :moved_post,
      :linked, :granted_badge
    )
  end

  def self.mark_posts_read(user, topic_id, post_numbers)
    Notification.where(user_id: user.id, topic_id: topic_id, post_number: post_numbers, read: false).update_all "read = 't'"
  end

  def self.interesting_after(min_date)
    result =  where("created_at > ?", min_date)
              .includes(:topic)
              .unread
              .limit(20)
              .order("CASE WHEN notification_type = #{Notification.types[:replied]} THEN 1
                           WHEN notification_type = #{Notification.types[:mentioned]} THEN 2
                           ELSE 3
                      END, created_at DESC").to_a

    # Remove any duplicates by type and topic
    if result.present?
      seen = {}
      to_remove = Set.new

      result.each do |r|
        seen[r.notification_type] ||= Set.new
        if seen[r.notification_type].include?(r.topic_id)
          to_remove << r.id
        else
          seen[r.notification_type] << r.topic_id
        end
      end
      result.reject! {|r| to_remove.include?(r.id) }
    end

    result
  end

  # Clean up any notifications the user can no longer see. For example, if a topic was previously
  # public then turns private.
  def self.remove_for(user_id, topic_id)
    Notification.where(user_id: user_id, topic_id: topic_id).delete_all
  end

  # Be wary of calling this frequently. O(n) JSON parsing can suck.
  def data_hash
    @data_hash ||= begin
      return nil if data.blank?
      JSON.parse(data).with_indifferent_access
    end
  end

  def text_description
    link = block_given? ? yield : ""
    I18n.t("notification_types.#{Notification.types[notification_type]}", data_hash.merge(link: link))
  end

  def url
    if topic.present?
      return topic.relative_url(post_number)
    end
  end

  def post
    return if topic_id.blank? || post_number.blank?

    Post.find_by(topic_id: topic_id, post_number: post_number)
  end

  def self.recent_report(user, count = nil)
    count ||= 10
    notifications = user.notifications.recent(count).includes(:topic).to_a

    if notifications.present?
      notifications += user.notifications
        .order('created_at desc')
        .where(read: false, notification_type: Notification.types[:private_message])
        .where('id < ?', notifications.last.id)
        .limit(count)

      notifications.sort do |x,y|
        if x.unread_pm? && !y.unread_pm?
          -1
        elsif y.unread_pm? && !x.unread_pm?
          1
        else
          y.created_at <=> x.created_at
        end
      end.take(count)
    else
      []
    end

  end

  def unread_pm?
    Notification.types[:private_message] == self.notification_type && !read
  end

  protected

  def refresh_notification_count
    user.publish_notifications_state
  end

end

# == Schema Information
#
# Table name: notifications
#
#  id                :integer          not null, primary key
#  notification_type :integer          not null
#  user_id           :integer          not null
#  data              :string(1000)     not null
#  read              :boolean          default(FALSE), not null
#  created_at        :datetime         not null
#  updated_at        :datetime         not null
#  topic_id          :integer
#  post_number       :integer
#  post_action_id    :integer
#
# Indexes
#
#  index_notifications_on_post_action_id          (post_action_id)
#  index_notifications_on_user_id_and_created_at  (user_id,created_at)
#