(ns metabase.notification.payload.core
  (:require
   [metabase.channel.render.core :as channel.render]
   [metabase.models.notification :as models.notification]
   [metabase.notification.payload.execute :as notification.payload.execute]
   [metabase.public-settings :as public-settings]
   [metabase.util :as u]
   [metabase.util.malli :as mu]
   [metabase.util.malli.schema :as ms]
   [potemkin :as p]
   [toucan2.core :as t2]))
(p/import-vars
 [notification.payload.execute
  process-virtual-dashcard])

Schema for the notification.

(def Notification
  ;; TODO: how do we make this schema closed after :merge?
  [:merge #_{:closed true}
   [:map
    [:payload_type                  (into [:enum] models.notification/notification-types)]
    ;; allow unsaved notification to be sent
    [:id           {:optional true} [:maybe ms/PositiveInt]]
    [:active       {:optional true} :boolean]
    [:created_at   {:optional true} :any]
    [:updated_at   {:optional true} :any]]
   [:multi {:dispatch :payload_type}
    ;; system event is a bit special in that part of the payload comes from the event itself
    [:notification/system-event
     [:map
      [:payload
       [:map {:closed true}
        ;; TODO: event-info schema for each event type
        [:event_topic [:fn #(= "event" (-> % keyword namespace))]]
        [:event_info  [:maybe :map]]]]]]
    [:notification/card
     [:map
      ;; replacement of pulse
      [:alert      [:map
                    [:card_id                           ms/PositiveInt]
                    [:schedule                          :map]
                    [:alert_condition  {:optional true} [:enum "rows" "goal"]]
                    [:alert_above_goal {:optional true} [:maybe ms/BooleanValue]]
                    [:alert_first_only {:optional true} [:maybe ms/BooleanValue]]
                    [:include_csv      {:optional true} [:maybe ms/BooleanValue]]
                    [:include_xls      {:optional true} [:maybe ms/BooleanValue]]
                    [:format_rows      {:optional true} [:maybe ms/BooleanValue]]
                    [:pivot_results    {:optional true} [:maybe ms/BooleanValue]]]]
      [:creator_id ms/PositiveInt]]]
    [:notification/dashboard
     [:map
      [:creator_id ms/PositiveInt]
      ;; replacement of pulse
      [:dashboard_subscription #_{:optional true}
       [:map
        [:dashboard_id ms/PositiveInt]
        [:parameters {:optional true} [:maybe [:sequential :map]]]
        [:dashboard_subscription_dashcards {:optional true}
         [:sequential [:map
                       [:card_id                        [:maybe ms/PositiveInt]]
                       [:include_csv   {:optional true} [:maybe ms/BooleanValue]]
                       [:include_xls   {:optional true} [:maybe ms/BooleanValue]]
                       [:format_rows   {:optional true} [:maybe ms/BooleanValue]]
                       [:pivot_results {:optional true} [:maybe ms/BooleanValue]]]]]]]]]
    ;; for testing only
    [:notification/testing :map]]])

Schema for the notification payload.

(def NotificationPayload
  ;; TODO: how do we make this schema closed after :merge?
  [:merge
   [:map
    [:payload_type (into [:enum] models.notification/notification-types)]
    [:context      [:map]]]
   [:multi {:dispatch :payload_type}
    ;; override payload to add extra-context key
    [:notification/system-event
     [:map
      ;; override the payload with extra context
      [:payload
       [:map {:closed true}
        [:event_topic                   [:fn #(= "event" (-> % keyword namespace))]]
        [:event_info                    [:maybe :map]]
        [:custom       {:optional true} [:maybe :map]]]]]]
    [:notification/dashboard
     [:map
      [:payload [:map
                 [:dashboard_parts             [:sequential notification.payload.execute/Part]]
                 [:dashboard                   :map]
                 [:dashboard_subscription      :map]
                 [:style                       :map]
                 [:parameters {:optional true} [:maybe [:sequential :map]]]]]]]
    [:notification/card
     [:map
      [:payload [:map
                 [:card_part [:maybe notification.payload.execute/Part]]
                 [:card      :map]
                 [:style     :map]
                 [:alert     :map]]]]]
    [:notification/testing   :map]]])

Return the URL for the application logo. If the logo is the default, return a URL to the Metabase logo.

(defn- logo-url
  []
  (let [url (public-settings/application-logo-url)]
    (cond
      (= url "app/assets/img/logo.svg") "http://static.metabase.com/email_logo.png"
      ;; NOTE: disabling whitelabeled URLs for now since some email clients don't render them correctly
      ;; We need to extract them and embed as attachments like we do in metabase.channel.render.image-bundle
      ;; (data-uri-svg? url)               (themed-image-url url color)
      :else nil)))

Return a CSS style string for a button with the given color.

(defn- button-style
  [color]
  (str "display: inline-block; "
       "box-sizing: border-box; "
       "padding: 0.5rem 1.375rem; "
       "font-size: 1.063rem; "
       "font-weight: bold; "
       "text-decoration: none; "
       "cursor: pointer; "
       "color: #fff; "
       "border: 1px solid " color "; "
       "background-color: " color "; "
       "border-radius: 4px;"))
(defn- default-context
  []
  ;; DO NOT delete or rename these fields, they are used in the notification templates
  {:application_name     (public-settings/application-name)
   :application_color    (public-settings/application-color)
   :application_logo_url (logo-url)
   :site_name            (public-settings/site-name)
   :site_url             (public-settings/site-url)
   :admin_email          (public-settings/admin-email)
   :style                {:button (button-style (channel.render/primary-color))}})

Given a notification info, return the notification payload.

(defmulti payload
  :payload_type)
(mu/defn notification-payload :- NotificationPayload
  "Realize notification-info with :context and :payload."
  [notification :- Notification]
  (assoc (select-keys notification [:payload_type])
         :creator (t2/select-one [:model/User :id :first_name :last_name :email] (:creator_id notification))
         :payload (payload notification)
         :context (default-context)))

Determine whether a notification should be sent. Default to true.

(defmulti should-send-notification?
  :payload_type)
(defmethod should-send-notification? :default
  [_notification-payload]
  true)

------------------------------------------------------------------------------------------------;; Load the implementations ;; ------------------------------------------------------------------------------------------------;;

(when-not *compile-files*
  (u/find-and-load-namespaces! "metabase.notification.payload.impl"))