(ns metabase.query-processor.middleware.update-used-cards
  (:require
   [java-time.api :as t]
   [metabase.lib.metadata :as lib.metadata]
   [metabase.query-processor.schema :as qp.schema]
   [metabase.query-processor.store :as qp.store]
   [metabase.util.grouper :as grouper]
   [metabase.util.log :as log]
   [metabase.util.malli :as mu]
   ^{:clj-kondo/ignore [:discouraged-namespace]}
   [toucan2.core :as t2]))
(set! *warn-on-reflection* true)
(def ^:private update-used-card-interval-seconds 20)
(defn- update-used-cards!*
  [card-id-timestamps]
  (let [card-id->timestamp (update-vals (group-by :id card-id-timestamps)
                                        (fn [xs] (apply t/max (map :timestamp xs))))]
    (log/debugf "Update last_used_at of %d cards" (count card-id->timestamp))
    (try
      (t2/update! :model/Card :id [:in (keys card-id->timestamp)]
                  {:last_used_at (into [:case]
                                       (mapcat (fn [[id timestamp]]
                                                 [[:= :id id] [:greatest [:coalesce :last_used_at (t/offset-date-time 0)] timestamp]])
                                               card-id->timestamp))
                   ;; Set updated_at to its current value to prevent it from updating automatically
                   :updated_at :updated_at})
      (catch Throwable e
        (log/error e "Error updating used cards")))))
(defonce ^:private
  update-used-cards-queue
  (delay
    (grouper/start!
     update-used-cards!*
     :capacity 500
     :interval (* update-used-card-interval-seconds 1000))))
(mu/defn update-used-cards! :- ::qp.schema/qp
  "Middleware that get all card-ids that were used during a query execution and updates their `last_used_at`.
  Should be used after query is fully preprocessed.
  Including but not limited to cards used as:
  - the source card for other queries
  - definition for sandbox rules
  - card references in native query
  - dashcard on dashboard
  - alert/pulse"
  [qp :- ::qp.schema/qp]
  (mu/fn [query :- ::qp.schema/query
          rff   :- ::qp.schema/rff]
    (let [now  (t/offset-date-time)
          rff* (fn [metadata]
                 (doseq [card-id (distinct (lib.metadata/invoked-ids (qp.store/metadata-provider) :metadata/card))]
                   (grouper/submit! @update-used-cards-queue {:id        card-id
                                                              :timestamp now}))
                 (rff metadata))]
      (qp query rff*))))