(ns metabase-enterprise.stale
  (:require [malli.experimental.time]
            [metabase.embed.settings :as embed.settings]
            [metabase.models.setting :refer [defsetting]]
            [metabase.public-settings :as public-settings]
            [metabase.util.honey-sql-2 :as h2x]
            [metabase.util.i18n :refer [deferred-tru]]
            [metabase.util.malli :as mu]
            [toucan2.core :as t2]))
(set! *warn-on-reflection* true)
(def ^:private FindStaleContentArgs
  [:map
   [:collection-ids [:set {:doc "The set of collection IDs to search for stale content."} [:maybe :int]]]
   [:cutoff-date [:time/local-date {:doc "The cutoff date for stale content."}]]
   [:limit  [:maybe {:doc "The limit for pagination."} :int]]
   [:offset [:maybe {:doc "The offset for pagination."} :int]]
   [:sort-column  [:enum {:doc "The column to sort by."} :name :last_used_at]]
   [:sort-direction  [:enum {:doc "The direction to sort by."} :asc :desc]]])

Find stale content of a given model type.

(defmulti ^:private find-stale-query
  (fn [model _args] model))
(defmethod find-stale-query :model/Card
  [_model args]
  {:select [:report_card.id
            [(h2x/literal "Card") :model]
            [:report_card.name :name]
            :last_used_at]
   :from :report_card
   :left-join [:moderation_review [:and
                                   [:= :moderation_review.moderated_item_id :report_card.id]
                                   [:= :moderation_review.moderated_item_type (h2x/literal "card")]
                                   [:= :moderation_review.most_recent true]
                                   [:= :moderation_review.status (h2x/literal "verified")]]
               :pulse_card [:= :pulse_card.card_id :report_card.id]
               :pulse [:and
                       [:= :pulse_card.pulse_id :pulse.id]
                       [:= :pulse.archived false]]
               :sandboxes [:= :sandboxes.card_id :report_card.id]
               :collection [:= :collection.id :report_card.collection_id]]
   :where [:and
           [:= :sandboxes.id nil]
           [:= :pulse.id nil]
           [:= :moderation_review.id nil]
           [:= :report_card.archived false]
           [:<= :report_card.last_used_at (-> args :cutoff-date)]
           ;; find things only in regular collections, not the `instance-analytics` collection.
           [:= :collection.type nil]
           (when (embed.settings/some-embedding-enabled?)
             [:= :report_card.enable_embedding false])
           (when (public-settings/enable-public-sharing)
             [:= :report_card.public_uuid nil])
           [:or
            (when (contains? (:collection-ids args) nil)
              [:is :report_card.collection_id nil])
            [:in :report_card.collection_id (-> args :collection-ids)]]]})
(defmethod find-stale-query :model/Dashboard
  [_model args]
  {:select [:report_dashboard.id
            [(h2x/literal "Dashboard") :model]
            [:report_dashboard.name :name]
            [:last_viewed_at :last_used_at]]
   :from :report_dashboard
   :left-join [:pulse [:and
                       [:= :pulse.archived false]
                       [:= :pulse.dashboard_id :report_dashboard.id]]
               :collection [:= :collection.id :report_dashboard.collection_id]
               :moderation_review [:and
                                   [:= :moderation_review.moderated_item_id :report_dashboard.id]
                                   [:= :moderation_review.moderated_item_type (h2x/literal "dashboard")]
                                   [:= :moderation_review.most_recent true]
                                   [:= :moderation_review.status (h2x/literal "verified")]]]
   :where [:and
           [:= :pulse.id nil]
           [:= :moderation_review.id nil]
           [:= :report_dashboard.archived false]
           [:<= :report_dashboard.last_viewed_at (-> args :cutoff-date)]
           ;; find things only in regular collections, not the `instance-analytics` collection.
           [:= :collection.type nil]
           (when (embed.settings/some-embedding-enabled?)
             [:= :report_dashboard.enable_embedding false])
           (when (public-settings/enable-public-sharing)
             [:= :report_dashboard.public_uuid nil])
           [:or
            (when (contains? (:collection-ids args) nil)
              [:is :report_dashboard.collection_id nil])
            [:in :report_dashboard.collection_id (-> args :collection-ids)]]]})
(defn- sort-column [column]
  (case column
    :name :%lower.name
    :last_used_at :last_used_at))
(defn- queries [args]
  (for [model [:model/Card :model/Dashboard]]
    (find-stale-query model args)))
(mu/defn ^:private rows-query [{:keys [limit offset] :as args} :- FindStaleContentArgs]
  (cond-> {:select [:id :model]
           :from [[{:union-all (queries args)} :dummy_alias]]
           :order-by [[(sort-column (:sort-column args))
                       (:sort-direction args)]]}
    (some? limit) ;; limit
    (assoc :limit limit)
    (some? offset) ;; offset
    (assoc :offset offset)))
(mu/defn ^:private total-query [args :- FindStaleContentArgs]
  {:select [[:%count.* :count]]
   :from [[{:union-all (queries args)} :dummy_alias]]})
(mu/defn find-candidates :- [:map
                             [:rows [:sequential [:map
                                                  [:id pos-int?]
                                                  [:model keyword?]]]]
                             [:total :int]]
  "Find stale content in the given collections.
  Arguments are defined by [[FindStaleContentArgs]]:
  - `collection-ids`: the set of collection IDs to look for stale content in. Non-recursive, the exact set you pass in
  will be searched
  - `cutoff-date`: if something was last accessed before this date, it is 'stale'
  - `limit` / `offset`: to support pagination
  - `sort-column`: one of `:name` or `:last_used_at` (column to sort on)
  - `sort-direction`: `:asc` or `:desc`
  Returns a map containing two keys,
  - `:rows` (a collection of maps containing an `:id` and `:model` field, like `{:id 1 :model :model/Card}`), and
  - `:total` (the total count of stale elements that could be found if you iterated through all pages)
  "
  [{:keys [collection-ids] :as args} :- FindStaleContentArgs]
  (when (contains? collection-ids :root) (throw (ex-info "not implemented." {:collection-ids collection-ids})))
  {:rows (into []
               (comp
                (map #(select-keys % [:id :model]))
                (map (fn [v] (update v :model #(keyword "model" %)))))
               (t2/query (rows-query args)))
   :total (:count (t2/query-one (total-query args)))})
(defsetting dismissed-collection-cleanup-banner
  (deferred-tru "Was the collection cleanup banner dismissed?")
  :user-local :only
  :visibility :authenticated
  :type :boolean
  :default false
  :export? false)