;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns app.rpc.retry
  (:require
   [app.common.exceptions :as ex]
   [app.common.logging :as l]
   [app.util.services :as sv])
  (:import
   org.postgresql.util.PSQLException))

(defn conflict-exception?
  "Check if exception matches a insertion conflict on postgresql."
  [e]
  (when-let [cause (ex/instance? PSQLException e)]
    (= "23505" (.getSQLState ^PSQLException cause))))

(def ^:private always-false
  (constantly false))

(defn invoke!
  [{:keys [::max-retries] :or {max-retries 3} :as cfg} f & args]
  (loop [rnum 1]
    (let [match? (get cfg ::when always-false)
          result (try
                   (apply f cfg args)
                   (catch Throwable cause
                     (if (and (match? cause) (<= rnum max-retries))
                       ::retry
                       (throw cause))))]
      (if (= ::retry result)
        (let [label (get cfg ::label "anonymous")]
          (l/warn :hint "retrying operation" :label label :retry rnum)
          (recur (inc rnum)))
        result))))


(defn wrap-retry
  [_ f {:keys [::sv/name] :as mdata}]

  (if (::enabled mdata)
    (let [max-retries (get mdata ::max-retries 3)
          matches?    (get mdata ::when always-false)]
      (l/trc :hint "wrapping retry" :name name :max-retries max-retries)
      (fn [cfg params]
        (-> cfg
            (assoc ::max-retries max-retries)
            (assoc ::when matches?)
            (assoc ::label name)
            (invoke! f params))))
    f))

