(ns auto-ap.views.pages.ledger.profit-and-loss
  (:require
   [auto-ap.forms :as forms]
   [auto-ap.ledger.reports :as l-reports]
   [auto-ap.status :as status]
   [auto-ap.subs :as subs]
   [auto-ap.views.pages.ledger.report-table :as rtable]
   [auto-ap.views.components.buttons :as buttons]
   [auto-ap.views.components.layouts
    :refer [appearing-side-bar side-bar-layout]]
   [auto-ap.views.components.modal :as modal]
   [auto-ap.views.components.switch-field :refer [switch-field]]
   [auto-ap.views.pages.data-page :as data-page]
   [auto-ap.views.pages.ledger.side-bar :refer [ledger-side-bar]]
   [auto-ap.views.pages.ledger.table :as ledger-table]
   [auto-ap.views.utils
    :refer [date->str
            date-picker
            dispatch-event
            local-today
            query-params
            standard
            str->date
            with-user]]
   [cljs-time.core :as t]
   [clojure.set :as set]
   [clojure.string :as str]
   [re-frame.core :as re-frame]
   [react-dom :as react-dom]
   [reagent.core :as reagent]
   [vimsical.re-frame.cofx.inject :as inject]
   [vimsical.re-frame.fx.track :as track]
   [auto-ap.forms.builder :as form-builder]
   [auto-ap.views.components :as com]))



(defn and-last-year [{:keys [start end] :as p1}]
  [p1
   {:start (t/minus start (t/years 1))
    :end (t/minus end (t/years 1))}])

(defn encode-period [p]
  {:start (date->str (:start p) standard)
   :end (date->str (:end p) standard)})


;; SUBS 

(re-frame/reg-sub
 ::ledger-list-active?
 (fn [db]
   (-> db ::ledger-list-active?)))

(re-frame/reg-event-db
 ::received
 [(forms/in-form ::form)]
 (fn [db [_ data]]
   (-> db (assoc :report (:profit-and-loss data)))))

;; EVENTS

(re-frame/reg-event-db
 ::ledger-list-closing
 (fn [db]
   (assoc db ::ledger-list-active? false)))

(re-frame/reg-event-fx
 ::report-requested
 [with-user (forms/in-form ::form)]
 (fn [{:keys [db user]}]
   (cond-> {:graphql {:token user
                      :owns-state {:single ::page}
                      :query-obj {:venia/queries [[:profit-and-loss
                                                   {:client-ids (filter identity (map (comp :id :client) (:clients (:data db))))
                                                    :periods (mapv #(select-keys % #{:start :end} ) (:periods (:data db)))
                                                    :include-deltas  (:include-deltas (:data db))
                                                    :column-per-location (:column-per-location (:data db))}
                                                   [[:periods [[:accounts [:name :amount :client_id :account-type :id :count :numeric-code :location]]]]]]]} 
                      :on-success [::received]}
            :set-uri-params {:periods (mapv
                                        encode-period
                                        (:periods (:data db)))
                             :clients (mapv #(select-keys (:client %) [:name :id]) (:clients (:data db))) }
            :db (-> db
                    (dissoc :report)
                    (update-in [:data :clients] #(into [] (filter seq %))))})))

(defn email-body [report-url]
  (js/encodeURIComponent
    (str
      "Hello,
Click here (" report-url ") to download your financial reports. We have not finished reviewing and reconciling these numbers with you. Please review and let us know if anything seems missing or in need of correction.
Click here (http://app.integreatconsult.com/) to login to the Financials app to review the details here.
Click here (https://share.vidyard.com/watch/MHTo5PyXPxXUpVH93RWFM9?) for a video on how to run a P&L on your own.
To see a history of past financial reports, click here: https://app.integreatconsult.com/reports/

NOTE: Please review the transactions we may have question for you here: https://app.integreatconsult.com/transactions/requires-feedback. You can either edit the transaction to what expense account it should be or email back what it should be.")))

(re-frame/reg-event-fx
  ::received-pdf
  [(re-frame/inject-cofx ::inject/sub [::subs/clients-by-id])]
  (fn [{:keys [db ::subs/clients-by-id]} [_ result]]
    (let [selected-clients (-> db ::forms/forms ::form :data :clients)
          single-client? (= (count selected-clients)
                            1)
          client-emails (when single-client?
                         (-> clients-by-id
                             (get (:id (:client (first selected-clients))))
                             :emails))]
      {:dispatch [::modal/modal-requested {:title "Your report is ready"
                                           :body  [:div
                                                   [:div "Click "
                                                    [:a {:href (-> result :profit-and-loss-pdf :url) :target "_new"} "here"] " to view it."]
                                                   (when (and single-client? (seq client-emails) @(re-frame/subscribe [::subs/is-admin?]))
                                                     [:div "Once you've confirmed you're happy with it, click "
                                                      [:a {:href (str "mailto:" (str/join ";" (map :email client-emails)) "?body=" (email-body (-> result :profit-and-loss-pdf :url))
                                                                      "&subject=" (-> result  :profit-and-loss-pdf :name) " is ready")}
                                                       "here"] " to open your email client and to send it to " (str/join "," (map (fn [e]
                                                                                                                                    (str (:email e) " (" (:description e) ")"))
                                                                                                                                  client-emails)) "."])]}]})))
(re-frame/reg-event-fx
 ::export-pdf
 [with-user (forms/in-form ::form)]
 (fn [{:keys [db user]}]
   (cond-> {:graphql {:token user
                      :owns-state {:single ::page}
                      :query-obj {:venia/queries [[:profit-and-loss-pdf
                                                   {:client-ids (filter identity (map (comp :id :client) (:clients (:data db))))
                                                    :include-deltas  (:include-deltas (:data db))
                                                    :column-per-location (:column-per-location (:data db))
                                                    :periods (mapv #(select-keys % #{:start :end}) (:periods (:data db)))}
                                                   [:url :name]]]} 
                      :on-success [::received-pdf]}
            :set-uri-params {:periods (mapv encode-period (:periods (:data db)))
                             :clients (mapv #(select-keys (:client %) [:name :id]) (:clients (:data db))) }
            :db (dissoc db :report)})))


(re-frame/reg-event-db
 ::change-internal
 (forms/change-handler ::form
                       (fn  [_ field value]
                         (cond
                           (= [:periods] field)
                           [field value]

                           (and (= :periods (first field))
                                (= 2 (count field)))
                           [field value] ;;already serialized

                           (= :periods (first field)) [field value ]

                           :else nil)
                         )))

(re-frame/reg-event-fx
  ::change
  [with-user (forms/in-form ::form)]
  (fn [{:keys [db]} [_ & event]]
    {:db (dissoc db :report)
     :dispatch-n [(into [::change-internal] event)
                  [::ledger-list-closing]]}))


(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)
     :numeric-code (:numeric-code params)
     :location (:location params)
     :locations (:locations params)
     :date-range (:date-range params)}))

(re-frame/reg-event-fx
 ::ledger-params-change
 [with-user]
 (fn [{:keys [user]} [_ ledger-params]]
   (when (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
                                                                  :note
                                                                  :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
 ::investigate-clicked
 (fn [{:keys [db]} [_ {:keys [location numeric-code client-id locations]
                       {:keys [start end]} :date-range}]]
   {:db       (-> db (assoc ::ledger-list-active? true))
    :dispatch [::data-page/additional-params-changed ::ledger {:client-id         client-id
                                                               :numeric-code numeric-code
                                                               :location          location
                                                               :locations locations
                                                               :date-range        {:start (date->str start standard)
                                                                                   :end   (date->str end standard)}}]}))


(re-frame/reg-sub
 ::can-submit
 (fn [_]
   true))


(defn report-control-detail [{:keys [active box which]} children]
  (when (and @box
             (= which @active)) 
    (react-dom/createPortal (reagent/as-element
                             [:div.notification.is-light
                              [:a.delete {:on-click (fn [] (reset! active nil))}]
                              children
                              ])
                            @box)))

(defn period-preset-button [{:keys [title periods]}]
  (let [{{:keys [selected-preset]} :data} @(re-frame/subscribe [::forms/form ::form])]
    [:div.control 
     [:a.button
      {:class    (when (= selected-preset title) "is-active")
       :on-click (fn []
                   (re-frame/dispatch-sync [::change 
                                            [:periods]
                                            periods
                                            [:selected-preset] title])
                   (re-frame/dispatch-sync [::change 
                                            [:show-advanced?]
                                            false]))}
      title]]))

(defn report-controls []
  (let [!box   (atom nil)
        active (reagent/atom nil)]
    (fn []
      (let [{:keys [data]}                                                       @(re-frame/subscribe [::forms/form ::form])
            {:keys [selected-preset include-deltas column-per-location]} data]
        [form-builder/builder {:can-submit   [::can-submit]
                               :change-event [::change]
                               :submit-event [::report-requested]
                               :id           ::form}
         [:div.report-controls
          [:div.level.mb-2
           [:div.level-left
            [:div.level-item
             [buttons/dropdown {:on-click (fn [] (reset! active :clients))}
              [:span {:style {"max-width" "30em"
                              "overflow" "hidden"
                              "text-overflow" "ellipsis"}}
               (str "Companies"
                    (when-let [clients (:clients data)]
                      (str " (" (str/join ", " (map (comp :name :client) clients)) ")")))]]
             [report-control-detail {:active active :box !box :which :clients}
              [:div {:style {:width "20em"}}
               [:h4.subtitle "Companies"]
               [form-builder/raw-field-v2 {:field :clients}
                [com/multi-field-v2 {:new-text "Add another company"
                                     :template [[form-builder/raw-field-v2 {:field :client}
                                                 [com/entity-typeahead {:entities     @(re-frame/subscribe [::subs/clients])
                                                                        :style        {:width "18em"}
                                                                        :entity->text :name}]]]
                                     :key-fn :id}]]]]]
            [:div.level-item
             [buttons/dropdown {:on-click (fn [] (reset! active :range))}
              [:span (str "Range"
                          (when selected-preset
                            (str " (" selected-preset ")")))]]
             [report-control-detail {:active active :box !box :which :range}
              [:div 
               [:h4.subtitle "Range"]
               [:div.field.is-grouped
                [:div.control 
                 [:div.field.has-addons
                  [:div.control
                   [form-builder/raw-field-v2 {:field :thirteen-periods-end}
                    [date-picker {:placeholder "End date"
                                  :output      :cljs-date}]]]
                  [period-preset-button {:title   "13 periods"
                                         :periods (let [today (or (some-> (:thirteen-periods-end data))
                                                                  (local-today))]
                                                    (into 
                                                      [{:start (t/plus (t/minus today (t/weeks (* 13 4)))
                                                                       (t/days 1))
                                                        :end   today
                                                        :title "Total"}]
                                                      (for [i (range 13)]
                                                        {:start (t/plus (t/minus today (t/weeks (* (inc i) 4)))
                                                                        (t/days 1))
                                                         :end   (t/minus today (t/weeks (* i 4)))})))}]]]

                [:div.control 
                 [:div.field.has-addons
                  [:div.control
                   [form-builder/raw-field-v2 {:field :twelve-periods-end}
                    [date-picker {:placeholder "End date"
                                  :output      :cljs-date}]]]
                  [period-preset-button {:title   "12 months"
                                         :periods (let [end-date   (or (some-> (:twelve-periods-end data))
                                                                       (local-today))
                                                        this-month (t/local-date (t/year end-date)
                                                                                 (t/month end-date)
                                                                                 1)]
                                                    (into
                                                      [{:start (t/minus this-month (t/months 11))
                                                        :end   (t/minus (t/plus this-month (t/months 1))
                                                                        (t/days 1))
                                                        :title "Total"}]
                                                      (for [i (range 12)]
                                                        {:start (t/minus this-month (t/months (- 11 i)))
                                                         :end   (t/minus (t/minus this-month (t/months (- 10 i)))
                                                                         (t/days 1))})))}]]]

                [period-preset-button {:periods (let [last-sunday (loop [current (local-today)]
                                                                    (if (= 7 (t/day-of-week current))
                                                                      current
                                                                      (recur (t/minus current (t/period :days 1)))))]
                                                  (and-last-year {:start (t/minus last-sunday (t/period :days 6))
                                                                  :end   last-sunday}))
                                       :title   "Last week"}]

                [period-preset-button {:periods (and-last-year {:start (loop [current (local-today)]
                                                                         (if (= 1 (t/day-of-week current))
                                                                           current
                                                                           (recur (t/minus current (t/period :days 1)))))
                                                                :end   (local-today)})
                                       :title   "Week to date"}]

                [period-preset-button {:periods (and-last-year {:start (t/minus (t/local-date (t/year (local-today))
                                                                                              (t/month (local-today))
                                                                                              1)
                                                                                (t/period :months 1))
                                                                :end   (t/minus (t/local-date (t/year (local-today))
                                                                                              (t/month (local-today))
                                                                                              1)
                                                                                (t/period :days 1))})
                                       :title   "Last month"}]

                [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today))
                                                                                     (t/month (local-today))
                                                                                     1)
                                                                :end   (local-today)})
                                       :title   "Month to date"}]

                [period-preset-button {:periods (and-last-year {:start (t/local-date (t/year (local-today)) 1 1)
                                                                :end 
                                                                (local-today)})
                                       :title   "Year to date"}]

                [period-preset-button {:periods [{:start (t/local-date (dec (t/year (local-today))) 1 1)
                                                  :end   (t/local-date (dec (t/year (local-today))) 12 31)}]
                                       :title   "Last calendar year"}]

                [period-preset-button {:periods (and-last-year {:start (t/plus (t/minus (local-today) (t/period :years 1))
                                                                               (t/period :days 1))
                                                                :end   (local-today)})
                                       :title   "Full year"}]]
               [:div
                [form-builder/raw-field-v2 {:field :show-advanced?}
                 [com/checkbox {:label "Show Advanced"}]]]
               (when (:show-advanced? data) 
                 [form-builder/raw-field-v2 {:field :periods}
                  [com/multi-field-v2 {:template [[form-builder/raw-field-v2 {:field :start}
                                                   [date-picker {:output :cljs-date}]]
                                                  [form-builder/raw-field-v2 {:field :end}
                                                   [date-picker {:output :cljs-date}]]]}]])]]]

            [:div.level-item
             [:div
              [switch-field {:id        "include-deltas"
                             :checked   (boolean include-deltas)
                             :on-change (fn [e]
                                          (re-frame/dispatch [::change
                                                              [:include-deltas] (.-checked (.-target e))]))
                             :label     "Include deltas"
                             :type      "checkbox"}]]]
            [:div.level-item
             [:div
              [switch-field {:id        "column-per-location"
                             :checked   (boolean column-per-location)
                             :on-change (fn [e]
                                          (re-frame/dispatch [::change
                                                              [:column-per-location] (.-checked (.-target e))]))
                             :label     "Column per location"
                             :type      "checkbox"}]]]]
           [:div.level-right
            [:div.buttons 

             [:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]
             [:button.button.is-primary "Run"]]

            ]]
          [:div.report-control-detail {:ref (fn [el]
                                              (when (not= @!box el)
                                                (reset! !box el)))}]]]))))



(defn pnl-report [{:keys [args report-data]}]
  (let [pnl-data (->> report-data
                      :periods
                      (mapcat (fn [p1 p2]
                                (map
                                  (fn [a]
                                    (assoc a :period p1
                                           :amount (js/parseFloat (:amount a)))
                                    )
                                  (:accounts p2)))
                              (:periods args)))
        client-codes        (->> @(re-frame/subscribe [::subs/clients-by-id])
                                 (map (fn [[k v]]
                                        [k (:code v)]))
                                 (into {}))
        pnl-data            (l-reports/->PNLData args pnl-data client-codes)
        report              (l-reports/summarize-pnl pnl-data)
        table (rtable/concat-tables (concat (:summaries report) (:details report)))]
    [:div
     [:h1.title "Profit and Loss - " (str/join ", " (map (comp :name :client) (:clients args)))]
     (when (:warning report)
       [:div.notification.is-warning.is-light
        (:warning report)])
     [rtable/table {:widths      (into [20] (take (dec (rtable/cell-count table))
                                                  (mapcat identity
                                                          (repeat
                                                            (if (-> pnl-data :args :include-deltas)
                                                              [13 6 13]
                                                              [13 6])))))
                    :click-event ::investigate-clicked
                    :table       table}]]))


(defn profit-and-loss-content []
  (let [status                @(re-frame/subscribe [::status/single ::page])
        {:keys [data report]} @(re-frame/subscribe [::forms/form ::form])]
    [:div 
     [:div
      [status/status-notification {:statuses [[::status/single ::page]]}]
      [report-controls]]
     [status/big-loader status]
     (when (and (not= :loading (:state status))
                report)
       [pnl-report {:report-data report
                    :args        data}])]))

(re-frame/reg-event-fx
 ::unmounted-pnl
 (fn [{:keys [_]} _]
   {::track/dispose {:id ::ledger-params}}))

(re-frame/reg-event-fx
 ::mounted-pnl
 (fn [{:keys [db]} _]
   (let [qp (query-params)]
     {:db (forms/start-form db ::form {:periods (->> qp
                                                     :periods
                                                     (mapv (fn [period]
                                                             {:start (str->date (:start period) standard)
                                                              :end (str->date (:end period) standard)})))
                                       :clients (mapv (fn [c] {:client c :id (random-uuid)})
                                                     (or (:clients qp)
                                                         [(some-> @(re-frame/subscribe [::subs/client]) (select-keys [:name :id]) )]))
                                       :include-deltas false
                                       :show-advanced? false})
      ::track/register {:id           ::ledger-params
                        :subscription [::data-page/params ::ledger]
                        :event-fn     (fn [params] [::ledger-params-change params])}})))

(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 profit-and-loss-page []
  (reagent/create-class
   {:display-name "profit-and-loss-page"
    :component-did-mount #(re-frame/dispatch [::mounted-pnl])
    :component-will-unmount #(re-frame/dispatch [::unmounted-pnl])
    :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 [profit-and-loss-content]
            :right-side-bar [appearing-side-bar
                             {:visible? ledger-list-active?}
                             [ledger-list]]}]
          [:div "Not authorized"])))}))
