(ns auto-ap.views.pages.ledger.balance-sheet
  (:require [auto-ap.subs :as subs]
            [auto-ap.views.components.layouts :refer [side-bar-layout appearing-side-bar]]
            [auto-ap.views.components.switch-field :refer [switch-field]]
            [auto-ap.views.pages.ledger.table :as ledger-table ]
            [goog.string :as gstring]
            [vimsical.re-frame.fx.track :as track]
            [auto-ap.utils :refer [by]]
            [auto-ap.views.pages.data-page :as data-page]
            [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
            [auto-ap.views.utils :refer [date->str date-picker bind-field local-now standard ->$ str->date dispatch-event with-user]]
            [cljs-time.core :as t]
            [re-frame.core :as re-frame]
            [reagent.core :as reagent]
            [clojure.set :as set]
            [auto-ap.status :as status]))

(defn data-params->query-params [params]
  (when params 
    {:start (:start params 0)
     :sort (:sort params)
     :per-page (:per-page params)
     :vendor-id (:id (:vendor params))
     :client-id (:client-id params )
     :from-numeric-code (:from-numeric-code params)
     :to-numeric-code (:to-numeric-code params)
     :date-range (:date-range params)}))

(re-frame/reg-sub
 ::report
 (fn [db]
   (-> db ::report)))

(re-frame/reg-sub
 ::accounts
 (fn [db [_ type]]
   (->> db
        ::report
        :balance-sheet-accounts
        (map #(update % :amount js/parseFloat))
        (filter (fn [{:keys [account-type]}]
                  (= type account-type)))
        (sort-by :numeric-code))))

(re-frame/reg-sub
 ::accounts-by-id
 (fn [db [_ type]]
   (->> db
        ::report
        :balance-sheet-accounts
        (map #(update % :amount js/parseFloat))
        (filter (fn [{:keys [account-type]}]
                  (= type account-type)))
        (by :id))))

(re-frame/reg-sub
 ::comparable-accounts-by-id
 (fn [db [_ type]]
   (->> db
        ::report
        :comparable-balance-sheet-accounts
        (map #(update % :amount js/parseFloat))
        (filter (fn [{:keys [account-type]}]
                  (= type account-type)))
        (by :id))))

(re-frame/reg-sub
 ::ledger-list-active?
 (fn [db]
   (-> db ::ledger-list-active?)))
(def groupings
  {:asset [["1100 Cash and Bank Accounts" 11000 11999] 
           ["1200 Accounts Receivable" 12000 12999]
           ["1300 Inventory" 13000 13999]
           ["1400 Prepaid Expenses" 14000 14999]
           ["1500 Property and Equipment" 15000 15999]
           ["1600 Intangible Assets" 16000 16999]
           ["1700 Other Assets" 17000 19999]]
   :liability [["2000 Accounts Payable" 20100 23999]
               ["2400 Accrued Expenses" 24000 24999]
               ["2500 Other Liabilities" 25000 25999]
               ["2600 Split Accounts" 26000 26999]
               ["2700 Current Portion of Long-Term Debt" 27000 27999]
               ["2800 Notes Payable" 28000 28999]
               ]
   :equity [["3000 Owner's Equity" 30000 39999]]
   :revenue [["Retained Earnings" 40000 49999]]})

(re-frame/reg-event-db
 ::received
 (fn [db [_ data]]
   (-> db
       (assoc ::report (:balance-sheet data)))))

(re-frame/reg-sub
 ::params
 (fn [db]
   (-> db ::params)))

(re-frame/reg-sub
 ::include-comparison
 :<- [::params]
 (fn [params]
   (:include-comparison params)))

(re-frame/reg-event-fx
 ::params-change
 (fn [cofx [_ params]]
   {:db (-> (:db cofx)
            (dissoc ::report)
            (assoc-in [::params] params))
    :graphql {:token (-> cofx :db :user)
              :query-obj {:venia/queries [[:balance-sheet
                                           (assoc params
                                                  :client-id (:id @(re-frame/subscribe [::subs/client])))
                                           [[:balance-sheet-accounts [:name :amount :account-type :id :numeric-code]]
                                            [:comparable-balance-sheet-accounts [:name :amount :account-type :id :numeric-code]]]]]}

              :owns-state {:single ::page}
              :on-success [::received]}}))

(re-frame/reg-event-fx
 ::date-picked
 (fn [cofx [_ _ date]]
   {:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
                                      :date
                                      date)]}))

(re-frame/reg-event-fx
 ::comparison-date-picked
 (fn [cofx [_ _ date]]
   {:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
                                      :comparison-date
                                      date)]}))

(re-frame/reg-event-fx
 ::include-comparison-changed
 (fn [cofx [_ include-comparison]]
   {:dispatch [::params-change (assoc @(re-frame/subscribe [::params])
                                      :include-comparison
                                      include-comparison)]}))

(re-frame/reg-event-fx
 ::investigate-clicked
 (fn [{:keys [db]} [_ from-numeric-code to-numeric-code which]]
   (let [date (if (= :current which)
                (get @(re-frame/subscribe [::params]) :date)
                (get @(re-frame/subscribe [::params]) :comparison-date))]
     {:db (-> db (assoc ::ledger-list-active? true))
      :dispatch [::data-page/additional-params-changed ::ledger {:client-id (:id @(re-frame/subscribe [::subs/client]))
                                                                 :from-numeric-code from-numeric-code
                                                                 :to-numeric-code to-numeric-code
                                                                 :date-range {:start "2000-01-01"
                                                                              :end date}}]})))

(re-frame/reg-event-fx
 ::ledger-params-change
 [with-user]
 (fn [{:keys [user db]} [_ ledger-params]]
   
   (if (seq ledger-params)
     {:graphql {:token user
                :owns-state {:single [::data-page/page ::ledger]}
                :query-obj {:venia/queries [[:ledger-page
                                             {:filters (data-params->query-params ledger-params)}
                                             [[:journal-entries [:id
                                                                 :source
                                                                 :original-entity
                                                                 :amount
                                                                 :alternate-description
                                                                 [:vendor
                                                                  [:name :id]]
                                                                 [:client
                                                                  [:name :id]]
                                                                 [:line-items
                                                                  [:id :debit :credit :location :running-balance
                                                                   [:account [:id :name]]]]
                                                                 :date]]
                                              :total
                                              :start
                                              :end]]]} 
                :on-success (fn [result]
                              
                              [::data-page/received ::ledger (set/rename-keys (:ledger-page result)
                                                                              {:journal-entries :data})])}})))

(re-frame/reg-event-fx
 ::unmounted-balance-sheet
 (fn [{:keys [db]} _]
   {:dispatch [::data-page/dispose ::ledger]
    ::track/dispose {:id ::ledger-params}}))

(re-frame/reg-event-fx
 ::mounted-balance-sheet
 (fn [{:keys [db]} _]
   {::track/register {:id ::ledger-params
                      :subscription [::data-page/params ::ledger]
                      :event-fn (fn [params] [::ledger-params-change params])}}))

(defn grouping [{:keys [header accounts comparable-accounts groupings]}]
  (let [include-comparison @(re-frame/subscribe [::include-comparison])]
    
    (for [[grouping-name from to] groupings
          :let [matching-accounts (filter
                                   #(<= from (:numeric-code %) to)
                                   accounts)]
          :when (seq matching-accounts)
          ]
      (list 
       [:tr [:th "---" grouping-name "---"]
        [:td]
        (when include-comparison
          [:td])
        (when include-comparison
          [:td])
        ]
       (for [account matching-accounts]
         [:tr [:td (:name account)]
          [:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked (:numeric-code account) (:numeric-code account) :current])}
                               (->$ (:amount account))] ]
          (when include-comparison
            [:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked (:numeric-code account) (:numeric-code account) :comparable])}
                                 (->$ (:amount (get comparable-accounts (:id account)) 0))]])
          (when include-comparison
            [:td.has-text-right (->$ (- (:amount account ) (:amount (get comparable-accounts (:id account)) 0)))])])
       [:tr [:th "---" grouping-name "---"]
        [:th.has-text-right.total [:a {:on-click (dispatch-event [::investigate-clicked from to :current])}
                                   (->$ (reduce + 0 (map :amount
                                                         matching-accounts)))] ]
        (when include-comparison
          [:th.has-text-right.total [:a {:on-click (dispatch-event [::investigate-clicked from to :comparable])}
                                      (->$ (reduce + 0 (map #(:amount (get comparable-accounts (:id %)) 0)
                                                                                                      matching-accounts)))]])
        (when include-comparison
          [:th.has-text-right.total (->$ (reduce + 0
                                                 (map #(- (:amount % ) (:amount (get comparable-accounts (:id %)) 0))
                                                      matching-accounts)))])
        [:td]
        ]))))

(defn overall-grouping [type title]
  (let [include-comparison @(re-frame/subscribe [::include-comparison])]
    (list 
     [:tr [:th.has-text-centered title]
      [:td]
      (when include-comparison
        [:td])
      (when include-comparison
        [:td])]
     (grouping {:accounts @(re-frame/subscribe [::accounts type])
                :groupings (type groupings)
                :comparable-accounts @(re-frame/subscribe [::comparable-accounts-by-id type])
                })
     [:tr [:th.has-text-centered title]
      [:th.has-text-right (->$ (reduce + 0 (map :amount @(re-frame/subscribe [::accounts type]))))]
      (when include-comparison
        [:th.has-text-right (->$ (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id type])))))])
      (when include-comparison
        [:th.has-text-right (->$ (- (reduce + 0 (map :amount @(re-frame/subscribe [::accounts type])))
                                    (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id type]))))))])])))


(defn retained-earnings []
  
  (let [include-comparison @(re-frame/subscribe [::include-comparison])]
    (list 
     
     #_(grouping {:accounts @(re-frame/subscribe [::accounts type])
                  :comparable-accounts @(re-frame/subscribe [::comparable-accounts-by-id type])
                  })
     [:tr [:th.has-text-centered "Retained Earnings"]
      [:th.has-text-right (->$ (- (reduce + 0 (map :amount @(re-frame/subscribe [::accounts :revenue])))
                                  (reduce + 0 (map :amount @(re-frame/subscribe [::accounts :expense])))))]
      (when include-comparison
        [:th.has-text-right (->$ (- (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :revenue]))))
                                    (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :expense]))))))])
      (when include-comparison
        [:th.has-text-right (->$ (- (- (reduce + 0 (map :amount @(re-frame/subscribe [::accounts :revenue])))
                                       (reduce + 0 (map :amount @(re-frame/subscribe [::accounts :expense]))))
                                    (- (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :revenue]))))
                                       (reduce + 0 (map :amount (vals @(re-frame/subscribe [::comparable-accounts-by-id :expense])))))))])])))

(def balance-sheet-content
  (with-meta
    (fn []
      (let [current-client @(re-frame/subscribe [::subs/client])
            status @(re-frame/subscribe [::status/single ::page])
            user @(re-frame/subscribe [::subs/user])
            params @(re-frame/subscribe [::params])]
        (if current-client
          [:div.is-inline
           [:h1.title "Balance Sheet - " (:name current-client)]
           [status/status-notification {:statuses [[::status/single ::page]]}]

           [:div.report-controls
            
            [:div.level 
             [:div.level-left 
              [:div.level-item 
               [:div.control 
                [:p.help "Date"]
                [bind-field
                 [date-picker {:class-name "input"
                               :class "input"
                               :format-week-number (fn [] "")
                               :previous-month-button-label ""
                               :placeholder "mm/dd/yyyy"
                               :next-month-button-label ""
                               :next-month-label ""
                               :type "date"
                               :field [:date]
                               :event [::date-picked]
                               :popper-props (clj->js {:placement "right"})
                               :subscription params}]]]]
              [:div.level-item 
               

               [:div.control 
                [:div.mt-3] 
                [switch-field {:id "include-comparison"
                               :checked (boolean (:include-comparison params))
                               :on-change (fn [e]
                                            (re-frame/dispatch [::include-comparison-changed (.-checked (.-target e))]))
                               :label "Include comparison"
                               :type "checkbox"}]]]
              [:div.level-item 

               (when (boolean (:include-comparison params))
                 [:div.control
                  
                  [:p.help "Comparison Date"]
                  [bind-field
                   [date-picker {:class-name "input"
                                 :class "input"
                                 :format-week-number (fn [] "")
                                 :previous-month-button-label ""
                                 :placeholder "mm/dd/yyyy"
                                 :next-month-button-label ""
                                 :next-month-label ""
                                 :type "date"
                                 :field [:comparison-date]
                                 :event [::comparison-date-picked]
                                 :popper-props (clj->js {:placement "right"})
                                 :subscription params}]]])
               ]]]]
           [status/big-loader status]
           (when (not= :loading (:state status))
             [:table.table.compact.balance-sheet
              [:tr
               [:td.has-text-right "Period ending"]
               (when (:include-comparison params)
                 [:td.has-text-right (date->str (str->date (:date params) standard))])
               (when (:include-comparison params)
                 [:td.has-text-right (when (:date params)
                                       (date->str (str->date (:comparison-date params) standard)))])
               [:td]]
              (list 
               (overall-grouping :asset "Assets")
               (overall-grouping :liability "Liabilities" )
               (overall-grouping :equity "Owner's Equity" )
               (retained-earnings))])]
          [:div
           [:h1.title "Balance sheet"]
           [:h2.subtitle "Please choose a client first"]])))
    {:component-will-mount #(re-frame/dispatch-sync [::params-change {:date (date->str (local-now) standard)
                                                                      :comparison-date (date->str (t/minus (local-now) (t/years 1)) standard)
                                                                      :include-comparison true}]) }))

(defn ledger-list [_ ]
  [:div [:a.delete.is-pulled-right {:on-click (dispatch-event [::ledger-list-closing])}]
   [:div
    [:h1.title "Ledger entries"]
    [ledger-table/table {:id :ledger
                         :data-page ::ledger}]]])

(defn balance-sheet-page []
  (reagent/create-class
   {:display-name "balance-sheet-page"
    :component-did-mount #(re-frame/dispatch [::mounted-balance-sheet])
    :component-will-unmount #(re-frame/dispatch [::unmounted-balance-sheet])
    :reagent-render
    (fn []
      (let [ledger-list-active? @(re-frame/subscribe [::ledger-list-active?])
            user (re-frame/subscribe [::subs/user])]
        (if (not= "manager" (:user/role @user))
          [side-bar-layout
           {:side-bar [ledger-side-bar] 
            :main [balance-sheet-content]
            :right-side-bar [appearing-side-bar
                             {:visible? ledger-list-active?}
                             [ledger-list]]}]
          [:div "Not authorized"])))}))
