(ns metabase.events.notification
  (:require
   [malli.core :as mc]
   [malli.transform :as mtx]
   [metabase.events :as events]
   [metabase.models.notification :as models.notification]
   [metabase.models.task-history :as task-history]
   [metabase.notification.core :as notification]
   [metabase.util.log :as log]
   [methodical.core :as methodical]
   [toucan2.core :as t2]))
(derive :metabase/event ::notification)
(def ^:private supported-topics #{:event/user-invited
                                  :event/alert-create
                                  :event/slack-token-invalid})
(def ^:private hydrate-transformer
  (mtx/transformer
   {:decoders {:map {:compile (fn [schema _]
                                (let [hydrates (into {}
                                                     (keep (fn [[k {:keys [hydrate] :as _p} _v]]
                                                             (when hydrate
                                                               [k hydrate])))
                                                     (mc/children schema))]
                                  (when (seq hydrates)
                                    (fn [x]
                                      (if (map? x)
                                        (reduce-kv
                                         (fn [_acc k {:keys [key model] :as _hydrate-prop}]
                                           (assoc x key (t2/select-one model (get x k))))
                                         x
                                         hydrates)
                                        x)))))}}}))

Given a schema and value, hydrate the keys that are marked as to-hydrate. Hydrated keys have the :hydrate properties that can be added by [[metabase.events.schema/with-hydration]].

(hydrate! [:map [:user_id {:hydrate {:key :user :model [:model/User :email]}} :int]] {:user_id 1}) ;; => {:user_id 1 :user {:email "ngoc@metabase.com"}}

(defn- hydrate!
  [schema value]
  (mc/decode schema value hydrate-transformer))

Hydrate event-info if the topic has a schema.

(defn maybe-hydrate-event-info
  [topic event-info]
  (cond->> event-info
    (some? (events/topic->schema topic))
    (hydrate! (events/topic->schema topic))))

Returns notifications for a given topic if it is supported and has notifications.

(defn- notifications-for-topic
  [topic]
  (when (supported-topics topic)
    (models.notification/notifications-for-event topic)))

Used as a hack for when we need to skip sending notifications for certain events.

It's an escape hatch until we implement conditional notifications.

(def ^:dynamic *skip-sending-notification?*
  false)
(defn- maybe-send-notification-for-topic!
  [topic event-info]
  (when-not *skip-sending-notification?*
    (when-let [notifications (notifications-for-topic topic)]
      (task-history/with-task-history {:task         "notification-trigger"
                                       :task_details {:trigger_type     :notification-subscription/system-event
                                                      :event_name       topic
                                                      :notification_ids (map :id notifications)}}
        (log/infof "Found %d notifications for event: %s" (count notifications) topic)
        (doseq [notification notifications]
          (notification/send-notification! (assoc notification :payload {:event_info  (maybe-hydrate-event-info topic event-info)
                                                                         :event_topic topic})))))))
(methodical/defmethod events/publish-event! ::notification
  [topic event-info]
  (maybe-send-notification-for-topic! topic event-info))