(ns auto-ap.views.pages.home
  (:require [auto-ap.routes :as routes]
            [auto-ap.subs :as subs]
            [auto-ap.views.components.grid :as grid]
            [auto-ap.permissions :as p]
            [auto-ap.views.components.layouts :refer [side-bar-layout]]
            [auto-ap.views.components.vendor-dialog :as vendor-dialog]
            [auto-ap.history :refer [history]]
            [cemerick.url :as url]
            [auto-ap.views.utils
             :refer
             [->$ date->str days-until dispatch-event local-now standard]]
            [bidi.bidi :as bidi]
            [cljs-time.coerce :as coerce]
            [cljs-time.core :as t]
            [pushy.core :as pushy]
            [re-frame.core :as re-frame]
            [recharts]
            [reagent.core :as r]
            [react]
            [vimsical.re-frame.cofx.inject :as inject]
            [auto-ap.status :as status]))

(re-frame/reg-sub 
 ::client
 :<- [::subs/clients]
 :<- [::subs/client]
 (fn [[ clients client]]
   (or client
       (first clients))))

(def pie-chart (r/adapt-react-class recharts/PieChart))
(def pie (r/adapt-react-class recharts/Pie))
(def bar-chart (r/adapt-react-class recharts/BarChart))
(def x-axis (r/adapt-react-class recharts/XAxis))
(def y-axis (r/adapt-react-class recharts/YAxis))
(def bar (r/adapt-react-class recharts/Bar))
(def legend (r/adapt-react-class recharts/Legend))
(def cell (r/adapt-react-class recharts/Cell))
(def tool-tip (r/adapt-react-class recharts/Tooltip))

(def colors ["#79b52e" "#009cea" "#209b1c" "#f48017" " #ff0303" "hsl(217, 71%, 53%)" "hsl(141, 53%, 53%)"])
(def light-colors ["#a6d869" "#8ad8ff" "#2cd327" "#fac899" "#ff6b6b" "hsl(217, 71%, 53%)"])

(defn make-pie-chart
  [{:keys [width height data]}]
  [pie-chart {:width width
              :height height}
   [pie {:fill "#82ca9d"
         :data data
         :dataKey "value"
         :inner-radius 20}
    (map (fn [_ y]
           ^{:key y}
           [cell {:key y :fill (colors y)}]) data (range))]
   [tool-tip]
   [legend]])

(defn make-bar-chart [{:keys [width height data]}]
  [bar-chart {:width width :height height :data data :fill "#FFFFFF"}
   [tool-tip]
   [bar {:dataKey "paid" :fill (get colors 0) :stackId "a" :name "Paid"}]
   [bar {:dataKey "unpaid" :fill (get light-colors 0) :stackId "a" :name "Unpaid"}]
   [x-axis {:dataKey "name"}]
   [y-axis]
   [legend]])

(defn make-cash-flow-chart [{:keys [width height data]}]
  (let [redirect-fn (fn [x]
                      (pushy/set-token! history (str (bidi/path-for routes/routes :unpaid-invoices) "?" (get (js->clj x) "query-params"))))]
    [bar-chart {:width width :height height :data data :fill "#FFFFFF"  :stackOffset "sign"}
     [tool-tip]
     [bar {:dataKey "effective-balance" :fill (get colors 1) :stackId "a" :name "Effective Balance"
           :on-click redirect-fn}]
     [bar {:dataKey "outstanding-payments" :fill (get colors 0) :stackId "a" :name "Outstanding Payments"
           :on-click redirect-fn}]
     [bar {:dataKey "invoices" :fill (get colors 3) :stackId "a" :name "Invoices"
           :on-click redirect-fn}]
     [bar {:dataKey "credits" :fill (get colors 2) :stackId "a" :name "Upcoming Credits"
           :on-click redirect-fn}]
     [bar {:dataKey "debits" :fill (get colors 4) :stackId "a" :name "Upcoming Debits"
           :on-click redirect-fn}]
     [x-axis {:dataKey "name"}]
     [y-axis]
     [legend]]))

(re-frame/reg-event-db
 ::received
 (fn [db [_ {:keys [expense-account-stats invoice-stats cash-flow]}]]
   (let [expense-account-stats (->> expense-account-stats
                                    (map #(update % :total (fn [t] (js/parseFloat t))))
                                    (sort-by :total)
                                    (reverse)
                                    (take 5))
         top-5 (vec (take 5 expense-account-stats))
         rest (drop 5 expense-account-stats)
         other {:account {:id 0 :name "Other"} :total (reduce + 0 (map :total rest))}]
     (cond-> db
       (seq top-5)
       (assoc ::top-expense-categories (conj top-5 other))

       (seq invoice-stats)
       (assoc ::invoice-stats invoice-stats)

       (seq cash-flow)
       (assoc ::cash-flow cash-flow)))))

(re-frame/reg-event-db
 ::select-cash-flow-range
 [(re-frame/path ::chart-options)]
 (fn [chart-options [_ which]]
   (assoc chart-options :cash-flow-range which)))

(re-frame/reg-sub
 ::invoice-stats
 (fn [db]
   (::invoice-stats db)))

(re-frame/reg-sub
 ::chart-options
 (fn [db]
   (merge {:cash-flow-range :seven-days} (::chart-options db))))

(re-frame/reg-sub
 ::top-expense-categories
 (fn [db]
   (::top-expense-categories db)))

(defn sum-by-date [pairs]
  (reduce
   (fn [result [date amount]]
     (let [due (if (t/before? date (local-now))
                 (local-now)
                 date)]
       (update result (date->str due)
               (fn [r] (+ (or r 0.0) (js/parseFloat amount))))))
   {}
   pairs))

(re-frame/reg-sub
 ::cash-flow-data
 (fn [db]
   (::cash-flow db)))

(re-frame/reg-sub
 ::cash-flow-table-params
 (fn [db]
   (::cash-flow-table-params db)))

(re-frame/reg-event-db
 ::cash-flow-table-params-changed
 (fn [db [_ new]]
   (assoc db ::cash-flow-table-params new)))

(re-frame/reg-sub
 ::cash-flow
 :<- [::chart-options]
 :<- [::cash-flow-data]
 (fn [[chart-options cash-flow-data]]
   (let [{:keys [outstanding-payments beginning-balance invoices-due-soon upcoming-credits upcoming-debits]} cash-flow-data
         invoices-due-soon (sum-by-date (map (fn [i] [(:due i) (:outstanding-balance i)]) invoices-due-soon))
         upcoming-credits (sum-by-date (map (fn [i] [(:date i) (:amount i)]) upcoming-credits))
         upcoming-debits (sum-by-date (map (fn [i] [(:date i) (:amount i)]) upcoming-debits))
         start-date (local-now)
         effective-balance (- beginning-balance outstanding-payments (invoices-due-soon (date->str start-date) 0.0))]

     (reverse
      (reduce
       (fn [[{:keys [effective-balance credits-yesterday]} :as acc] day]
         (let [invoices-due-today (invoices-due-soon (date->str (t/plus start-date (t/days day))) 0.0)
               credits-due-today (upcoming-credits (date->str (t/plus start-date (t/days day))) 0.0)
               debits-due-today (upcoming-debits (date->str (t/plus start-date (t/days day))) 0.0)
               today (t/plus start-date (t/days day))]
           (conj acc
                 {:name              (date->str today)
                  :date              today
                  :effective-balance (+ (- effective-balance invoices-due-today)
                                        debits-due-today
                                        credits-yesterday)
                  :credits-yesterday credits-due-today
                  :credits           credits-due-today
                  :debits            debits-due-today
                  :invoices          (- invoices-due-today)
                  :query-params      (url/map->query {:due-range {:start (date->str today standard)
                                                                  :end   (date->str today standard)}})})))
       (list {:name                 (date->str start-date)
              :date                 start-date
              :effective-balance    effective-balance
              :invoices             (- (invoices-due-soon (date->str start-date) 0.0))
              :credits              (upcoming-credits (date->str start-date) 0.0)
              :credits-yesterday    (upcoming-credits (date->str start-date) 0.0)
              :debits               (upcoming-debits (date->str start-date) 0.0)
              :outstanding-payments (- outstanding-payments)
              :query-params         (url/map->query {:due-range {:end  (date->str start-date standard)}})})
       (condp = (:cash-flow-range chart-options)
         :seven-days
         (range 1 7)
         :thirty-days
         (range 1 31)

         :sixty-days
         (range 1 61)

         :ninety-days
         (range 1 91)

         :one-hundred-twenty-days
         (range 1 121)

         :one-hundred-fifty-days
         (range 1 151)

         :one-hundred-eighty-days
         (range 1 181)))))))


(re-frame/reg-sub
 ::cash-flow-page
 :<- [::cash-flow-table-params]
 :<- [::cash-flow-data]
 (fn [[params cash-flow-data]]
   (let [{:keys [invoices-due-soon upcoming-credits upcoming-debits]} cash-flow-data
         rows (concat (map (fn [c]
                             {:date (:date c)
                              :days-until (days-until (:date c))
                              :name (or (:identifier c) "Bi-weekly Average")
                              :amount (:amount c)
                              :type "Debit"}) upcoming-debits)
                      (map (fn [c]
                             {:date (:date c)
                              :days-until (days-until (:date c))
                              :amount (:amount c)
                              :name (or (:identifier c) "Bi-weekly Average")
                              :type "Credit"})
                           upcoming-credits)
                      (map (fn [c]
                             {:date (:due c)
                              :days-until (days-until (:due c))
                              :amount (:outstanding-balance c)
                              :name (str (:name (:vendor c)) " (" (:invoice-number c) ")")
                              :type "Invoice"})
                           invoices-due-soon))]
     (assoc (grid/virtual-paginate-controls (:start params) (:per-page params) rows)
            :data (grid/virtual-paginate (:start params)
                                         (:per-page params)
                                         (sort-by (comp  coerce/to-date :date) rows))))))

(re-frame/reg-event-fx
 ::mounted
 [(re-frame/inject-cofx ::inject/sub [::client]) ]
 (fn [{:keys [db] ::keys [client]} _]
   (cond->
    {:db      (assoc db ::top-expense-categories nil
                     ::cash-flow nil
                     ::invoice-stats nil)}
     client (assoc :graphql {:token      (-> db :user)
                             :owns-state {:single ::page}
                             :query-obj  {:venia/queries [[:expense_account_stats
                                                           {:client-id (:id client)}
                                                           [[:account [:id :name]] :total]]
                                                          [:invoice_stats
                                                           {:client-id (:id client)}
                                                           [:name :paid :unpaid]]
                                                          [:cash-flow
                                                           {:client-id (:id client)}
                                                           [:beginning-balance
                                                            :outstanding-payments
                                                            [:invoices-due-soon [:due :outstanding-balance [:vendor [:id :name]] :invoice-number]]
                                                            [:upcoming-credits [:date :amount :identifier]]
                                                            [:upcoming-debits [:date :amount :identifier]]]]]}
                             :on-success [::received]}))))

(defn cash-flow-range-button [{:keys [name value chart-options]}]
  [:a.button {:class (when (= value (:cash-flow-range chart-options))
                       ["is-info" "is-selected"])
              :on-click (dispatch-event [::select-cash-flow-range value])}
   name])

(defn cash-flow-grid []
  (let [page @(re-frame/subscribe [::cash-flow-page])
        opc (fn [p]
              (re-frame/dispatch [::cash-flow-table-params-changed p]))
        params @(re-frame/subscribe [::cash-flow-table-params])]
    [grid/grid {:status {:state :complete}
                :on-params-change opc
                :params params
                :column-count 4}
     [grid/controls page]
     [grid/table {:style {:width "800px"}}
      [grid/header
       [grid/row {}
        [grid/header-cell {} "Date"]
        [grid/header-cell {} "Type"]
        [grid/header-cell {} "Name"]
        [grid/header-cell {:class "has-text-right"} "Amount"]]]
      [grid/body
       (for [[i {:keys [date days-until type name amount]}] (map vector (range) (:data page))]
         ^{:key i}
         [grid/row {}
          [grid/cell {}
           (if (> days-until 0)
             [:span.has-text-success days-until " days"]
             [:span.has-text-danger days-until " days"])
           [:i.is-size-7 " (" (date->str date) ")"]]
          [grid/cell {} (if (> date 0)
                          "Upcoming "
                          "Due ")
           type]
          [grid/cell {} name]
          [grid/cell {:class "has-text-right"} (->$ amount)]])]]]))

(defn home-content []
  (let [client @(re-frame/subscribe [::client])
        client-id (-> client :id)
        one-client  (not (-> @(re-frame/subscribe [::subs/client]) :id))
        chart-options @(re-frame/subscribe [::chart-options])
        state @(re-frame/subscribe [::status/single ::page])]
    ^{:key client-id}
    [side-bar-layout {:side-bar [:div]
                      :main [:div [:h1.title "Home"]
                             (when one-client
                               
                               [:h2.title.is-6 "Note: these reports are for "
                                (:name client) ". Please choose a specific customer for their report."])
                             (if (= :loading (:state state))
                                 [:div.loader.is-loading.big.is-centered]

                                 [:<>
                                  [:h1.title.is-4 "Top expense categories"]
                                  (let [expense-categories @(re-frame/subscribe [::top-expense-categories])]
                                    (make-pie-chart {:width 800 :height 500 :data (clj->js
                                                                                   (map (fn [x] {:name (:name (:account x)) :value (:total x)}) expense-categories))}))
                                  [:h1.title.is-4 "Upcoming Bills"]
                                  (make-bar-chart {:width 800 :height 500 :data (clj->js
                                                                                 @(re-frame/subscribe [::invoice-stats]))})

                                  [:h1.title.is-4 "Cash Flow"]
                                  [:div.buttons.has-addons
                                   [cash-flow-range-button {:name          "7 days"
                                                            :value         :seven-days
                                                            :chart-options chart-options}]
                                   [cash-flow-range-button {:name          "30 days"
                                                            :value         :thirty-days
                                                            :chart-options chart-options}]

                                   [cash-flow-range-button {:name          "60 days"
                                                            :value         :sixty-days
                                                            :chart-options chart-options}]

                                   [cash-flow-range-button {:name          "90 days"
                                                            :value         :ninety-days
                                                            :chart-options chart-options}]
                                   [cash-flow-range-button {:name          "120 days"
                                                            :value         :one-hundred-twenty-days
                                                            :chart-options chart-options}]
                                   [cash-flow-range-button {:name          "150 days"
                                                            :value         :one-hundred-fifty-days
                                                            :chart-options chart-options}]
                                   [cash-flow-range-button {:name          "180 days"
                                                            :value         :one-hundred-eighty-days
                                                            :chart-options chart-options}]]

                                  (make-cash-flow-chart {:width 800 :height 500
                                                         :data  (clj->js @(re-frame/subscribe [::cash-flow]))})

                                  [cash-flow-grid]])]}]))


(defn home-page []
  (let [client-id (-> @(re-frame/subscribe [::client]) :id)]
    (re-frame/dispatch [::mounted])
    ^{:key client-id} [home-content]))
    
(defn home-page-with-vendor []
  (let [client-id (-> @(re-frame/subscribe [::client]) :id)
        user @(re-frame/subscribe [::subs/user])]
    (re-frame/dispatch [::mounted])
    (when (p/can? user {:subject :vendor
                         :activity :create})
      (re-frame/dispatch [::vendor-dialog/started {}]))



    ^{:key client-id} [home-content]))
