(ns promesa-retry
  (:require
   ["util" :refer [format]]   
   [promesa.core :as p :refer [reject! resolve!] :refer-macros [alet await]]
   [promesa.async-cljs :refer-macros [async]]
   [cljs.test :as t :include-macros true]))

(defn backoff-duration [retries-count interval]
  (+ (* interval retries-count) (rand-int interval)))

(defn- internal-retry
  [p f retries-left retries-count interval]
  (-> (f)
      (p/catch (fn [err]
                 (js/console.error (format "Catch failure before retrying (%s retried left): %s" retries-left (.-message err)))
                 (if (= 0 retries-left)
                   (p/reject! p err)
                   (let [d (backoff-duration retries-count interval)]
                     (js/console.log (format "Will wait %s ms before retrying (%s retries left, tried %s times)" d retries-left retries-count))
                     (-> (p/delay d "Retry msg ")
                         (p/then (fn [msg]
                                   (internal-retry p f (dec retries-left) (inc retries-count) interval))))))))
      (p/catch (fn [err]));this catch is needed to get the promise returned by the the previous catch in case of a retry, we catch the error cause and do nothing with it
      (p/then (fn [return]
                (p/resolve! p return))))
  p)

(defn retry
  ([f retries-left]
   (retry f retries-left 1000))
  ([f retries-left interval]
   (internal-retry (p/promise) f retries-left 1 interval)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;        Test               ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(def counter (atom 0))

(defn dummy-function []
  (async
   (if (< @counter 6)
     (do (println "Dummy function failed!")
         (swap! counter inc)
         (throw (ex-info "it failed !" {:counter @counter})))
     (println "yo it works!"))))

(t/deftest retry-test-success-in-the-end
  (reset! counter 0)
  (t/async done
           (-> (retry dummy-function 15 10)
               (p/catch (fn [err]
                          (t/is err)
                          (done)))
               (p/then (fn []
                         (js/console.log (format "Done retrying, success in the end"))
                         (done))))))

(t/deftest retry-test-failure-in-the-end
  (reset! counter 0)
  (t/async done
           (-> (retry dummy-function 3 10)
               (p/catch (fn [err]
                          (js/console.log "Done retrying, error in the end")
                          (t/is err)
                          (done)))
               (p/then (fn []
                         (js/console.log "Done retrying successfully")
                         (done)))
               )))