(ns auto-ap.effects
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require [re-frame.core :as re-frame]
            [cljs-http.client :as http]
            [cljs-time.coerce :as c]
            [cljs-time.core :as time]
            [cljs-time.format :as format]
            [cljs.core.async :refer [<! ] :as async]
            [clojure.string :as str]
            [clojure.walk :as walk]
            [venia.core :as v]
            [auto-ap.history :as p]
            [pushy.core :as pushy]))

(re-frame/reg-fx
 :redirect
 (fn [uri]
   (pushy/set-token! p/history uri)))

(re-frame/reg-fx
 :new-window
 (fn [url]
   (.open js/window url)))

(re-frame/reg-fx
 :set-local-storage
 (fn [[name value]]
   (if value
     (.setItem js/localStorage name value)
     (.removeItem js/localStorage name ))))

;; 2017-09-19T07:00:00.000Z
(def is-8601 #"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$")


(defn dates->date-times [x]
  (walk/postwalk
   (fn [node]
     (cond

       (and (string? node)
            (re-matches is-8601 node))
       (format/parse (format/formatters :date-time) node)

       (instance? js/Date node)
       (time/to-default-time-zone (c/from-date node))
       
       :else
       node))
   x))

(re-frame/reg-fx
 :http
 (fn [{:keys [method uri on-success on-error body headers token]}]
   (go
     (let [headers (if token
                     (assoc headers "Authorization" (str "Token " token))
                     headers)
           response (<! (http/request {:method method
                                       :body body
                                       :headers  headers
                                       :url uri}))]
       (if (>= (:status response) 400)
         (when on-error
           (->> response
               :body
               (dates->date-times)
               (conj on-error)
               (re-frame/dispatch)))
         (->> response
             :body
             (dates->date-times)
             (conj on-success)
             (re-frame/dispatch)))))))

(re-frame/reg-fx
 :https
 (fn [{:keys [requests on-success on-failure]}]
   (go
     
     (let [results (->>
                    (for [{:keys [method body headers uri token]} requests]
                      (go
                        (let [headers (if token
                                        (assoc headers "Authorization" (str "Token " token))
                                        headers)
                              response (<! (http/request {:method method
                                                          :body body
                                                          :headers  headers
                                                          :url uri}))]
                          (if (>= (:status response) 400)
                            :error
                            :success))))
                    (async/merge)
                    (async/reduce conj [])
                    (async/<!))]
       (if (some #{:error} results)
         (re-frame/dispatch on-failure)
         (re-frame/dispatch on-success))))))

(defn kebab->snake [s]
  (str/replace s #"-" "_"))

(defn snake [x]
  (if (namespace x)
    (keyword (namespace x) (kebab->snake (name x)))
    (keyword (kebab->snake (name x)))))

(defn ->graphql [m]
  (walk/postwalk
    (fn [node]
      (cond

        (keyword? node)
        (snake node)

        
        :else
        node))
    m))

(re-frame/reg-fx
 :graphql
 (fn [{:keys [query on-success on-error token variables query-obj]}]
   (go
     (let [headers (if token
                     {"Authorization" (str "Token " token)} 
                     {})
           query (or query (v/graphql-query (->graphql query-obj)))
           response (<! (http/request {:method :get
                                       :headers  headers
                                       :url (str "/api/graphql?query=" (js/encodeURIComponent query)
                                                 "&variables=" (pr-str (or variables {})))}))]
       (if (>= (:status response) 400)
         (when on-error
           (->> response
               :body
               :data
               (dates->date-times)
               (conj on-error)
               (re-frame/dispatch)))
         (->> response
             :body
             :data
             (dates->date-times)
             (conj on-success)
             (re-frame/dispatch)))))))
