(ns auto-ap.views.pages.invoices.form
  (:require
   [auto-ap.events :as events]
   [auto-ap.forms :as forms]
   [auto-ap.forms.builder :as form-builder]
   [auto-ap.schema :as schema]
   [auto-ap.status :as status]
   [auto-ap.subs :as subs]
   [auto-ap.time-utils :refer [next-dom]]
   [auto-ap.utils :refer [dollars=]]
   [auto-ap.views.components :as com]
   [auto-ap.views.components.dropdown :refer [drop-down]]
   [auto-ap.views.components.expense-accounts-field
    :as eaf
    :refer [expense-accounts-field-v2 recalculate-amounts]]
   [auto-ap.views.components.layouts :as layouts]
   [auto-ap.views.components.level :refer [left-stack]]
   [auto-ap.views.components.modal :as modal]
   [auto-ap.views.pages.invoices.common :refer [invoice-read]]
   [auto-ap.views.utils :refer [date-picker dispatch-event with-user date->str]]
   [cljs-time.core :as c]
   [clojure.string :as str]
   [malli.core :as m]
   [re-frame.core :as re-frame]
   [reagent.core :as r]
   [vimsical.re-frame.cofx.inject :as inject]
   [vimsical.re-frame.fx.track :as track]))

(def schema (m/schema
                           [:map
                            [:client schema/reference]
                            [:vendor schema/reference]
                            [:date schema/date]
                            [:due {:optional true} [:maybe schema/date]]
                            [:scheduled-payment {:optional true} [:maybe schema/date]]
                            [:invoice-number schema/not-empty-string]
                            [:total schema/money]
                            [:expense-accounts eaf/schema]]))

;; SUBS
(re-frame/reg-sub
 ::can-submit
 :<- [::forms/form ::form]
 (fn [{:keys [data]} _]
   (let [min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
                     nil
                     (- (:total (:original data)) (:outstanding-balance (:original data))))
         account-total (reduce + 0 (map :amount (:expense-accounts data)))]
     (and
       (or (not min-total) (>= (:total data) min-total))
       (or (not (:id data))
           (dollars= (Math/abs (:total data))  (Math/abs account-total)))))))

(re-frame/reg-sub
 ::create-query
 :<- [::forms/form ::form]
 (fn [{{:keys [invoice-number date due scheduled-payment location total expense-accounts vendor client]} :data}]
   {:venia/operation {:operation/type :mutation
                      :operation/name "AddInvoice"}
    :venia/queries [{:query/data [:add-invoice
                                  {:invoice {
                                             :vendor-id (:id vendor)
                                             :client-id (:id client)
                                             :date date
                                             :scheduled-payment scheduled-payment
                                             :due due
                                             :invoice-number (some-> invoice-number str/trim)
                                             :location location
                                             :total total
                                             :expense-accounts (map (fn [ea]
                                                                      {:id (when-not (str/starts-with? (:id ea) "new-")
                                                                             (:id ea))
                                                                       :account_id (:id (:account ea))
                                                                       :location (:location ea)
                                                                       :amount (:amount ea)})
                                                                    expense-accounts)}}
                                  invoice-read]}]}))

(re-frame/reg-sub
 ::edit-query
 :<- [::forms/form ::form]
 (fn [{{:keys [id invoice-number date due scheduled-payment total expense-accounts]} :data}]

   {:venia/operation {:operation/type :mutation
                      :operation/name "EditInvoice"}
    :venia/queries [{:query/data [:edit-invoice
                                  {:invoice {:id id
                                             :invoice-number invoice-number
                                             :date date
                                             :scheduled-payment scheduled-payment
                                             :due due
                                             :total total
                                             :expense-accounts (map (fn [ea]
                                                                      {:id (when-not (str/starts-with? (:id ea) "new-")
                                                                             (:id ea))
                                                                       :account_id (:id (:account ea))
                                                                       :location (:location ea)
                                                                       :amount (:amount ea)})
                                                                    expense-accounts)}}
                                  invoice-read]}]}))


;; EVENTS

(re-frame/reg-event-fx
 ::updated
 [
  (re-frame/inject-cofx ::inject/sub (fn [[_ _ _ client]]
                                       [::subs/locations-for-client (:id client)]))]
 (fn [{:keys [db] ::subs/keys [locations-for-client]} [_ _ command client]]
   (when (= :create command)
     {:db 
      (-> db
          (forms/stop-form ::form )
          (forms/start-form ::form {:client client
                                    :status :unpaid
                                    :date   (c/now)
                                    :expense-accounts
                                    (eaf/from-graphql []
                                                      0.0
                                                      locations-for-client)}))})))

(re-frame/reg-event-fx
 ::adding
 [(re-frame/inject-cofx ::inject/sub (fn [[_ which _]]
                                       [::subs/locations-for-client (:id (:client which))]))]
 (fn [{:keys [db] ::subs/keys [locations-for-client]} [_ new]]
   {:db 
    (-> db (forms/start-form ::form (assoc new :expense-accounts
                                           (eaf/from-graphql (:expense-accounts new)
                                                                                0.0
                                                                                locations-for-client))))}))
(re-frame/reg-event-fx
 ::editing
 [(re-frame/inject-cofx ::inject/sub (fn [[_ which _]]
                                       [::subs/locations-for-client (:id (:client which))]))]
 (fn [{:keys [db] ::subs/keys [locations-for-client]} [_ which vendor-preferences]]
   (let [edit-invoice which]
     {:db
      (-> db
          (forms/start-form ::form {:id                 (:id edit-invoice)
                                    :payments           (:payments edit-invoice)
                                    :status             (:status edit-invoice)
                                    :date               (:date edit-invoice)
                                    :due                (:due edit-invoice)
                                    :vendor-preferences vendor-preferences
                                    :scheduled-payment  (:scheduled-payment edit-invoice)
                                    :invoice-number     (:invoice-number edit-invoice)
                                    :total              (cond-> (:total edit-invoice)
                                                          (not (str/blank? (:total edit-invoice))) (js/parseFloat ))
                                    :original           which
                                    :vendor             (:vendor edit-invoice)
                                    :client             (:client edit-invoice)
                                    :expense-accounts   (eaf/from-graphql (:expense-accounts which)
                                                                          (:total which)
                                                                          locations-for-client)}))})))




(re-frame/reg-event-db
 ::changed
 (forms/change-handler ::form
                       (fn  [data field value]
                         (cond
                           (= [:vendor-preferences] field)
                           (cond-> []
                             (eaf/can-replace-with-default? (:expense-accounts data))
                             (into [[:expense-accounts] (eaf/default-account (:expense-accounts data)
                                                                                                (:default-account value)
                                                                                                (:total data)
                                                                                                (:locations (:client data)))])

                             (:automatically-paid-when-due value)
                             (into [[:scheduled-payment] (:due data)
                                    [:schedule-when-due] true])

                             (:schedule-payment-dom value)
                             (into [[:scheduled-payment] (next-dom (:date data) (:schedule-payment-dom value))]))

                           (= [:total] field)
                           [[:expense-accounts] (recalculate-amounts (:expense-accounts data) value)]

                           (and (= [:date] field)
                                (:schedule-payment-dom (:vendor-preferences data)))
                           [[:scheduled-payment] (next-dom value (:schedule-payment-dom (:vendor-preferences data))) ]

                           (and (= [:schedule-when-due] field) value)
                           [[:scheduled-payment] (:due data)]

                           (and (= [:due] field) (:schedule-when-due data))
                           [[:scheduled-payment] value]

                           :else
                           []))))
(re-frame/reg-event-db
 ::maybe-change-client
 [ (forms/in-form ::form) ]
 (fn [{:keys [data] :as f} [_ c]]
   (if (and (not (:id data))
            (:id c))
     (assoc-in f [:data :client] c)
     f)))

(re-frame/reg-event-fx
  ::add-and-print
  [with-user  (forms/in-form ::form)]
  (fn [{:keys [user]
        {{:keys [invoice-number date location total expense-accounts scheduled-payment vendor client]
          :as data} :data} :db} [_ bank-account-id type]]
    (if (not (m/validate schema data))
      {:dispatch-n [[::status/error ::form [{:message "Please fix the errors and try again."}]]
                    [::forms/attempted-submit ::form]]}
      {:graphql
       {:token      user
        :owns-state {:single ::form}
        :query-obj  {:venia/operation {:operation/type :mutation
                                       :operation/name "AddAndPrintInvoice"}
                     :venia/queries   [{:query/data [:add-and-print-invoice
                                                     {:invoice         {:date              date
                                                                        :vendor-id         (:id vendor)
                                                                        :client-id         (:id client)
                                                                        :scheduled-payment scheduled-payment
                                                                        :invoice-number    invoice-number
                                                                        :location          location
                                                                        :total             total
                                                                        :expense-accounts  (map (fn [ea]
                                                                                                  {:id         (when-not (str/starts-with? (:id ea) "new-")
                                                                                                                 (:id ea))
                                                                                                   :account_id (:id (:account ea))
                                                                                                   :location   (:location ea)
                                                                                                   :amount     (:amount ea)})
                                                                                                expense-accounts)}
                                                      :bank-account-id bank-account-id
                                                      :type            type}
                                                     [:pdf-url [:invoices invoice-read]]]}]} 
        :on-success [::added-and-printed client]
        :on-error   [::forms/save-error ::form]}})
    ))

(re-frame/reg-event-fx
 ::saving
 [with-user (forms/in-form ::form) (re-frame/inject-cofx ::inject/sub [::edit-query]) (re-frame/inject-cofx ::inject/sub [::create-query])]
 (fn [{:keys [user] {:keys [data]} :db ::keys [edit-query create-query]} _]
   (if (not (m/validate schema data))
     {:dispatch-n [[::status/error ::form [{:message "Please fix the errors and try again."}]]
                   [::forms/attempted-submit ::form]]}
     {:graphql
      {:token user
       :owns-state {:single ::form}
       :query-obj (if (:id data)
                    edit-query
                    create-query)
       :on-success (fn [result]
                     [::updated
                      (assoc (if (:id data)
                               (:edit-invoice result)
                               (:add-invoice result))
                             :class "live-added")
                      (if (:id data)
                        :edit
                        :create)
                      (:client data)])
       :on-error [::forms/save-error ::form]}})))

(re-frame/reg-event-fx
 ::save-requested
 [with-user (forms/in-form ::form)]
 (fn [{:keys [db]} [_ fwd-event]]
   (if (and (:scheduled-payment (:data db))
            (not (:vendor-autopay? (:vendor-preferences (:data db)))))
     {:dispatch 
      [::modal/modal-requested {:title "Scheduled payment date"
                                :body [:div "This vendor isn't set up to be automatically paid. On "
                                       (date->str (:scheduled-payment (:data db)))
                                       " the invoice will be marked as paid, but no payment will be made to the vendor. "
                                       "Are you sure you want to continue?"]
                                :confirm {:value "Save"
                                          :class "is-warning"
                                          :on-click #(do (re-frame/dispatch [::modal/modal-closed])
                                                         (re-frame/dispatch fwd-event))}
                                :cancel? true}]}
     {:dispatch fwd-event})))

(re-frame/reg-event-fx
 ::added-and-printed
 (fn [_ [_ client result]]
   (let [invoice (first (:invoices (:add-and-print-invoice result)))]
     {:dispatch-n [[::updated (assoc invoice :class "live-added") :create client]
                   [::checks-printed [invoice] (:pdf-url (:add-and-print-invoice result))]]})))

(re-frame/reg-event-db
 ::checks-printed
 (fn [db [_]]
   db))

(re-frame/reg-event-fx
  ::changed-vendor
  [(forms/in-form ::form)]
  (fn [{{{:keys [client]} :data} :db} [_ vendor]]
    (when (and (:id client) (:id vendor))
      {:dispatch [::events/vendor-preferences-requested {:client-id  (:id client)
                                                         :vendor-id  (:id vendor)
                                                         :on-success [::changed [:vendor-preferences]]
                                                         :on-failure [:hello]}]})))


;; VIEWS

(re-frame/reg-event-fx
 ::mounted
 (fn []
   {::track/register [{:id ::client
                       :subscription [::subs/client]
                       :event-fn (fn [c]
                                   [::maybe-change-client c])}
                      {:id ::vendor-change
                       :subscription [::forms/field ::form [:vendor]]
                       :event-fn (fn [v]
                                   [::changed-vendor v])}]}))

(re-frame/reg-event-fx
 ::unmounted
 (fn []
   {::track/dispose [{:id ::client}
                     {:id ::vendor-change}]}))

(defn form-content []
  [layouts/side-bar {:on-close   (dispatch-event [::forms/form-closing ::form ])}
   (let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
         can-submit? (boolean @(re-frame/subscribe [::can-submit]))
         status @(re-frame/subscribe [::status/single ::form])
         active-client @(re-frame/subscribe [::subs/client])
         exists? (:id data)
         can-change-amount? (#{:unpaid ":unpaid"} (:status data))
         min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
                     nil
                     (- (:total (:original data)) (:outstanding-balance (:original data))))]
     [form-builder/builder {:can-submit   [::can-submit]
                            :change-event [::changed]
                            :submit-event [::save-requested [::saving ]]
                            :id           ::form
                            :schema schema}

      [form-builder/section {:title [:div "New Invoice "
                                     (cond
                                       (#{:unpaid ":unpaid"} (:status data))
                                       nil

                                       (#{:voided ":voided"} (:status data))
                                       [:div.tag.is-info.is-light "Voided"]

                                       (and (#{:paid ":paid"} (:status data))
                                            (not (seq (:payments data))))
                                       [:div.tag.is-info.is-light "Automatically paid"]

                                       (#{:paid ":paid"} (:status data))
                                       (if-let [check-number (:check-number (:payment (first (:payments data))))]
                                         [:div.tag.is-info.is-light "Paid by check #" check-number  ]
                                         [:div.tag.is-info.is-light "Paid"])

                                       :else
                                       nil)]}

       (when-not active-client
         [form-builder/field-v2 {:required? true
                                 :field [:client]}
          "Client"
          [com/entity-typeahead {:entities     @(re-frame/subscribe [::subs/clients])
                                 :entity->text :name
                                 :style        {:width "18em"}
                                 :auto-focus   (if active-client false true)
                                 :disabled     exists?}]])
       [form-builder/field-v2 {:required? true
                               :field [:vendor]}
        "Vendor"
        [com/search-backed-typeahead {:disabled     exists?
                                  :search-query (fn [i]
                                                  [:search_vendor
                                                   {:query i}
                                                   [:name :id]])
                                  :style {:width "18em"}
                                  :auto-focus   (if active-client true false)}]]
       [form-builder/field-v2 {:required? true
                               :field     :date}
        "Date"
        [date-picker {:output :cljs-date}]]

       [form-builder/field-v2 {:field [:due]}
        "Due (optional)"
        [date-picker {:output :cljs-date}]]
       [form-builder/vertical-control 
        "Scheduled payment (optional)"
        [left-stack
         [:div.control
          [form-builder/raw-field-v2  {:field :scheduled-payment}
           [date-picker {:output :cljs-date}]]
          [form-builder/raw-error-v2 {:field :scheduled-payment}]]
         [:div.control
          [form-builder/raw-field-v2 {:field :schedule-when-due}

           [com/switch-input {:id    "schedule-when-due"
                          :label "Same as due date"}]]]]]
       [form-builder/field-v2 {:required? true
                            :field :invoice-number}
        "Invoice #"
        [:input.input {:style {:width "12em"}}]]

       [form-builder/field-v2 {:required? true
                               :field :total}
        "Total"
        [com/money-input {:disabled (if can-change-amount? "" "disabled")
                      :style {:max-width "8em"}
                      :min      min-total}]]]
      [form-builder/field-v2 {:field :expense-accounts}
       "Expense Accounts"
       [expense-accounts-field-v2 {:descriptor "expense account"
                                   :vendor-id (:id (:vendor data))
                                   :allowance :invoice
                                   :locations  (:locations (:client data))
                                   :max        (:total data)
                                   :client     (or (:client data) active-client)}]]
      [form-builder/error-notification]
      [:div {:style {:margin-bottom "1em"}}]
      [:div.columns
       (when-not exists?
         [:div.column
          [drop-down {:header [:button.button.is-primary-two.is-medium.is-fullwidth {:aria-haspopup true
                                                                                     :type          "button"
                                                                                     :on-click      (dispatch-event [::events/toggle-menu ::add-and-print-invoice ])
                                                                                     :disabled      (or (status/disabled-for status)
                                                                                                        (not can-submit?))
                                                                                     :class         (status/class-for status)}
                               "Pay "
                               [:span " "]
                               [:span.icon [:i.fa.fa-angle-down {:aria-hidden "true"}]]]
                      :class  "is-fullwidth"
                      :id     ::add-and-print-invoice}
           [:div 
            (list
              (for [{:keys [id name type]} (->> (:bank-accounts (:client data)) (filter :visible) (sort-by :sort-order))]
                (if (= :cash type)
                  ^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :cash]])} "With cash"]
                  (list
                    ^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :check]])} "Print checks from " name]
                    ^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :debit]])} "Debit from " name]))))]]])

       [:div.column
        [form-builder/submit-button {:class ["is-fullwidth"]}
         "Save"]]]])])


(defn form [_]
  (r/create-class
   {:display-name "invoice-form"
    :component-did-mount #(re-frame/dispatch [::mounted])
    :component-will-unmount #(re-frame/dispatch [::unmounted])
    :reagent-render (fn [p]
                      [form-content p])}))
