(ns auto-ap.events
  (:require [auto-ap.client-selection :refer [client-selection-schema
                                              client-selection-transformer]]
            [auto-ap.db :as db]
            [auto-ap.routes :as routes]
            [auto-ap.ssr-routes :as ssr-routes]
            [auto-ap.utils :refer [by]]
            [auto-ap.views.pages.data-page :as data-page]
            [auto-ap.views.utils :refer [gunzip parse-jwt with-user]]
            [bidi.bidi :as bidi]
            [cemerick.url :as url]
            [clojure.string :as str]
            [goog.crypt.base64 :as b64]
            [malli.core :as mc]
            [pako]
            [re-frame.core :as re-frame]))



(defn jwt->data [token]
  (let [raw (js->clj (.parse js/JSON (b64/decodeString (second (str/split token #"\.")))))
        gz-clients (or (:gz-clients raw)
                       (get raw "gz-clients"))]
    (cond-> raw
      gz-clients (assoc "user/clients" (gunzip gz-clients)))))



(defn client-query []
  (cond-> [:id :name :code :email :locations :feature-flags :groups
           [:emails [:id :email :description]]
           [:bank-accounts [:id :code :bank-name :name :type :visible
                            :locations :include-in-reports :current-balance
                            :sort-order]]]))

(defn client-detail-query [token]
  (cond-> [:id :name :signature-file :code :email :matches :week-a-debits :week-a-credits :week-b-debits :week-b-credits :locations :locked-until :square-auth-token :feature-flags
           [:square-integration-status [:last-updated :last-attempt :message :state :id]]
           [:square-locations [:square-id :id :name :client-location]]
           [:ezcater-locations [:id [:caterer [:name :id]] :location]]
           [:emails [:id :email :description]]
           [:location-matches [:id :location :match]]
           [:bank-accounts [:id :start-date :numeric-code :code :number :bank-name :bank-code :check-number :name :routing :type :sort-order :visible :yodlee-account-id
                            [:integration-status [:last-updated :last-attempt :message :state :id]]
                            [:yodlee-account [:name :id :number]]
                            [:plaid-account [:name :id :number]]
                            [:intuit-bank-account [:name :id :external-id]]
                            :use-date-instead-of-post-date
                            :locations :include-in-reports :current-balance :yodlee-balance-old]]
           [:address [:id :street1 :street2 :city :state :zip]]
           [:forecasted-transactions [:id :amount :identifier :day-of-month]]]
    (= "admin" (or (get (jwt->data token) "role") (get (jwt->data token) "user/role"))) (into [[:yodlee-provider-accounts [:id [:accounts [:id :name :number :available-balance]]]]
                                                                                               [:plaid-items [:id [:accounts [:id :name :number :balance]]]]])))




(re-frame/reg-event-fx
 ::initialize-db
 (fn  [{:keys [_]} [_ token]]
   (let [handler (:handler (bidi/match-route routes/routes (.. js/window -location -pathname)))
         last-client-id (.getItem js/localStorage "last-client-id")
         last-selected-clients (try (some->> "last-selected-clients"
                                             (.getItem js/localStorage)
                                             not-empty
                                             (.parse js/JSON)
                                             js->clj
                                             ( #(mc/decode client-selection-schema % client-selection-transformer)))
                                    (catch js/Error e
                                      :all))
         jwt-data (some-> token jwt->data)
         selected-client-assignment (cond (and token
                                               (= "admin" (get jwt-data "user/role"))
                                               (not last-selected-clients))
                                          :mine

                                          (and token
                                               last-client-id
                                               (not last-selected-clients))
                                          {:selected [(js/parseInt last-client-id)]}

                                          :else
                                          nil)]
     (cond
       (= :login handler)
       {:db (cond-> (assoc db/default-db
                           :active-route :login
                           :last-client-id last-client-id
                           :selected-clients last-selected-clients
                           :user nil))}

       (and (not= :login handler) (not token))
       {:redirect "/login"
        :db (assoc db/default-db
                   :active-route :login
                   :last-client-id last-client-id
                   :selected-clients last-selected-clients
                   :user token)}

       (and token (= "none" (or (get jwt-data "role") (get jwt-data "user/role"))))
       {:redirect "/needs-activation"
        :db (assoc db/default-db
                   :active-route :needs-activation
                   :last-client-id last-client-id
                   :selected-clients last-selected-clients
                   :user token)}

       :else
       (cond-> {:db (cond-> (assoc db/default-db
                                   :active-route handler
                                   :is-initial-loading? true
                                   :last-client-id last-client-id
                                   :selected-clients last-selected-clients
                                   :query-params (auto-ap.views.utils/query-params)
                                   :user token)
                      selected-client-assignment
                      (assoc :selected-clients selected-client-assignment))
                :graphql {:token token
                          :query-obj {:venia/queries [[:client (client-query)]]}
                          :on-success [::received-initial]
                          :on-error [::failed-initial]}}
         selected-client-assignment
         (assoc :set-local-storage ["last-selected-clients" (.stringify js/JSON selected-client-assignment)]))))))


(re-frame/reg-event-db
 ::toggle-menu
 (fn  [db [_ which]]
   (update-in db [:menu which :active?] #(not %))))


(re-frame/reg-event-fx
 ::received-initial
 (fn  [{:keys [db]} [_ {clients :client}]]
   (let [only-one-client (when (= 1 (count clients))
                           (->> clients first :id js/parseInt))]
     (when only-one-client
       (.setItem js/localStorage "last-client-id" only-one-client)
       (.setItem js/localStorage "last-selected-clients"
                 (.stringify js/JSON (clj->js {:selected [only-one-client]}))))
     {:db       (cond-> (-> db
                            (assoc :clients (by :id clients))
                            (assoc :is-initial-loading? false)
                            (assoc :client (or only-one-client
                                               (->> clients
                                                    (map :id)
                                                    (filter #(= % (:last-client-id db)))
                                                    first))))
                  only-one-client (assoc :last-client-id only-one-client
                                         :selected-clients {:selected [only-one-client]}))
      :interval {:action    :start
                 :id        :refresh-clients
                 :frequency 600000
                 :event     [::refresh-clients]}})))

(re-frame/reg-event-fx
 ::refresh-clients
 (fn [{:keys [db]}]
   (let [token (-> db :user)]
     {:graphql {:token token
                :query-obj {:venia/queries [[:client (client-query)]]}
                :on-success [::received-refreshed-clients]}})))

(re-frame/reg-event-fx
 ::received-refreshed-clients
 (fn [{:keys [db]} [_ {clients :client}]]
   {:db (assoc db :clients (by :id clients))}))

(re-frame/reg-event-db
 ::failed-initial
 (fn  [db [_ e]]
   (assoc db :initial-error e

          :is-initial-loading? false
          :active-route :initial-error)))

(re-frame/reg-event-fx
 ::swapped-client
 (fn [{:keys [db]} [_ client client-identifier]]
   (when (:id client)
     (.setItem js/localStorage "last-client-id" (:id client)))
   (.setItem js/localStorage "last-selected-clients"
             (.stringify js/JSON 
              (clj->js (condp = client-identifier
                         :all
                         :all

                         :mine
                         :mine

                         {:selected [(js/parseInt (:id client))]}))))

   {:db (assoc db :client (:id client)
               :selected-clients
               (condp = client-identifier
                 :all
                 :all

                 :mine
                 :mine

                 {:selected [(js/parseInt (:id client))]}))}))

(re-frame/reg-event-fx
 ::swap-client
 [with-user]
 (fn  [{:keys [db user]} [_ client]]
   (let [client-identifier (or (:id client) client)]
     {:http {:token  user
             :method :put
             :uri    (str (bidi/path-for ssr-routes/only-routes
                                         :active-client
                                         :request-method :put)
                          "?"
                          (url/map->query {:search-client client-identifier}))
             :headers {"x-clients"
                       (.stringify js/JSON
                                   (clj->js (cond (= :all client-identifier)
                                                  "all"
                                                  (= :mine client-identifier)
                                                  "mine"
                                                  :else
                                                  {:selected [client-identifier]})))}
             :on-success [::swapped-client client client-identifier]}})))

(re-frame/reg-event-fx
 ::set-active-route
 (fn  [{:keys [db]} [_ handler params route-params]]
   (cond
     (and (not= :login handler) (not (:user db)))
     {:redirect (bidi/path-for routes/routes :login)
      :db       (assoc db :active-route :login
                       :active-page :login
                       :menu nil
                       :page-failure nil)}

     (and (not= "admin" (:user/role (parse-jwt (:user db))))
          (str/includes? (name handler) "admin"))
     {:redirect (bidi/path-for routes/routes :index)
      :db       (assoc db :active-route :index
                       :active-page :index
                       :menu nil
                       :page-failure nil)}
     :else
     {:db (-> db
              (assoc :active-route handler
                     :auto-ap.views.components.modal/state nil
                     :page-failure nil
                     :menu nil
                     :query-params params
                     :route-params route-params)
              (data-page/dispose-all))})))


(re-frame/reg-event-fx
 ::logout
 (fn  [{:keys [db]} [_ logout-reason]]
   {:db (assoc db :user nil :menu {:client {:active? false}
                                   :account {:active? false}}
               :logout-reason logout-reason)
    :redirect (bidi/path-for routes/routes :login)
    :set-local-storage ["jwt" nil]}))


(re-frame/reg-event-db
 ::yodlee-merchants-received
 (fn [db [_ data]]
   (assoc db :yodlee-merchants (:yodlee-merchants data))))

(re-frame/reg-event-fx
 ::yodlee-merchants-needed
 (fn [{:keys [db]} _]
   {:graphql {:token (:user db)
              :query-obj {:venia/queries [[:yodlee-merchants
                                           [:name :yodlee-id :id]]]}
              :on-success [::yodlee-merchants-received]}}))

(re-frame/reg-event-fx
 ::vendor-preferences-requested
 [with-user]
 (fn [{:keys [user]} [_ {:keys [client-id vendor-id on-success on-failure owns-state]}]]
   {:graphql {:token      user
              :query-obj  {:venia/queries [[:vendor-by-id
                                            {:id vendor-id}
                                            [[:automatically-paid-when-due [:id]]
                                             [:schedule-payment-dom [[:client [:id]] :dom]]
                                             [:default-account [:id]]]]
                                           [:account-for-vendor
                                            {:vendor-id vendor-id
                                             :client-id client-id}
                                            [:name :id :numeric-code :location]]]}
              :owns-state owns-state
              :on-success (fn [r]
                            (let [schedule-payment-dom        (->> r
                                                                   :vendor-by-id
                                                                   :schedule-payment-dom
                                                                   (filter (fn [spd]
                                                                             (= (-> spd :client :id)
                                                                                client-id)))
                                                                   first
                                                                   :dom)
                                  automatically-paid-when-due (boolean ((->> r :vendor-by-id :automatically-paid-when-due (map :id) set) client-id))]
                              (conj on-success  {:default-account             (:account-for-vendor r)
                                                 :schedule-payment-dom        schedule-payment-dom
                                                 :automatically-paid-when-due automatically-paid-when-due
                                                 :vendor-autopay?             (or automatically-paid-when-due (boolean schedule-payment-dom))})))
              :on-failure on-failure}}))
