(ns auto-ap.views.pages.invoices.form
  (:require [auto-ap.entities.invoice :as invoice]
            [auto-ap.events :as events]
            [auto-ap.forms :as forms]
            [auto-ap.status :as status]
            [auto-ap.subs :as subs]
            [auto-ap.time-utils :refer [next-dom]]
            [auto-ap.utils :refer [by dollars=]]
            [auto-ap.views.components.dropdown :refer [drop-down]]
            [auto-ap.views.components.expense-accounts-field
             :as
             expense-accounts-field
             :refer
             [expense-accounts-field recalculate-amounts]]
            [auto-ap.views.components.layouts :as layouts]
            [auto-ap.views.components.modal :as modal]
            [auto-ap.views.components.money-field :refer [money-field]]
            [auto-ap.views.components.switch-field :refer [switch-field]]
            [auto-ap.views.components.typeahead :refer [typeahead-v3]]
            [auto-ap.views.pages.invoices.common :refer [invoice-read]]
            [auto-ap.views.utils
             :refer
             [date->str date-picker dispatch-event standard str->date with-user]]
            [cljs-time.core :as c]
            [clojure.spec.alpha :as s]
            [clojure.string :as str]
            [re-frame.core :as re-frame]
            [reagent.core :as r]
            [vimsical.re-frame.fx.track :as track]))

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

(re-frame/reg-sub
 ::create-query
 :<- [::forms/form ::form]
 (fn [{:keys [data] {:keys [id 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 {:date date
                                             :due due
                                             :vendor-id (:id vendor)
                                             :client-id (:id client)
                                             :scheduled-payment scheduled-payment
                                             :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 [data] {:keys [id invoice-number date due scheduled-payment location total expense-accounts vendor client]} :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]}]}))

(re-frame/reg-sub
 ::add-and-print-query
 (fn [db [_ bank-account-id type]]
   (let [{:keys [data] {:keys [id invoice-number date location total expense-accounts scheduled-payment vendor client]} :data} @(re-frame/subscribe [::forms/form ::form])]
     {: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]]]}]})))



;; EVENTS

(re-frame/reg-event-db
 ::updated
 (fn [db [_ invoice command]]
   (if (= :create command)
     (-> db
         (forms/stop-form ::form )
         (forms/start-form ::form {:client @(re-frame/subscribe [::subs/client])
                                   :status :unpaid
                                   :date (date->str (c/now) standard)}))
     db)))

(re-frame/reg-event-db
 ::adding
 (fn [db [_ new]]
   (let [locations @(re-frame/subscribe [::subs/locations-for-client (:client new)])
         accounts-by-id @(re-frame/subscribe [::subs/accounts-by-id (:client new)])]
     (-> db (forms/start-form ::form (assoc new :expense-accounts
                                            (expense-accounts-field/from-graphql (:expense-accounts new)
                                                                                 accounts-by-id
                                                                                 0.0
                                                                                 locations)))))))
(re-frame/reg-event-db
 ::editing
 (fn [db [_ which]]
   (let [accounts-by-id @(re-frame/subscribe [::subs/accounts-by-id (:client which)])
         vendor (get @(re-frame/subscribe [::subs/vendors-by-id]) (:id (:vendor which))) 
         edit-invoice (update which :date #(date->str % standard))
         edit-invoice (update edit-invoice :due #(date->str % standard))
         edit-invoice (update edit-invoice :scheduled-payment #(date->str % standard))
         edit-invoice (assoc edit-invoice :original edit-invoice)
         edit-invoice (assoc edit-invoice :vendor-autopay? (boolean ((set (map :id (:automatically-paid-when-due vendor)))
                                                                     (:id (:client which)))))
         locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client which))])
         ]
     (-> 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-autopay?  (boolean ((set (map :id (:automatically-paid-when-due vendor)))
                                                               (:id (:client which))))
                                   :scheduled-payment (:scheduled-payment edit-invoice)
                                   :invoice-number (:invoice-number edit-invoice)
                                   :total (:total edit-invoice)
                                   :original edit-invoice
                                   :vendor (:vendor edit-invoice)
                                   :client (:client edit-invoice)
                                   :expense-accounts (expense-accounts-field/from-graphql (:expense-accounts which)
                                                                                          accounts-by-id
                                                                                          (:total which)
                                                                                          locations)})))))




(re-frame/reg-event-db
 ::changed
 (forms/change-handler ::form
                       (fn  [data field value]
                         (let [locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])]
                           
                           (cond  (and (= [:vendor] field)
                                       value)
                                  (let [schedule-payment-dom (get (by (comp :id :client ) :dom (:schedule-payment-dom value))
                                                                  (:id (:client data)))]
                                    (cond-> []
                                      
                                      (expense-accounts-field/can-replace-with-default? (:expense-accounts data))
                                      (into [[:expense-accounts] (expense-accounts-field/default-account (:expense-accounts data)
                                                                                                         @(re-frame/subscribe [::subs/vendor-default-account (:id value) (:client data)])
                                                                                                         (:total data)
                                                                                                         locations)])

                                      
                                      (boolean ((set (map :id (:automatically-paid-when-due value))) (:id (:client data))))
                                      (into [[:scheduled-payment] (:due data)
                                             [:schedule-when-due] true
                                             [:vendor-autopay? ] true])


                                      schedule-payment-dom
                                      (into [[:scheduled-payment] (date->str (next-dom (str->date (:date data) standard) schedule-payment-dom) standard)
                                             [:vendor-autopay?] true])

                                      true
                                      (into [[:schedule-payment-dom] schedule-payment-dom])))

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

                                  (and (= [:date] field)
                                       (:schedule-payment-dom data))
                                  [[:scheduled-payment] (date->str (next-dom (str->date value standard) (:schedule-payment-dom data)) standard) ]

                                  (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 (:id data)
     f
     (assoc-in f [:data :client] c))))

(re-frame/reg-event-fx
 ::add-and-print
 [with-user  (forms/in-form ::form)]
 (fn [{:keys [user] {:keys [data]} :db} [_ bank-account-id type]]
   {:graphql
    {:token user
     :owns-state {:single ::form}
     :query-obj @(re-frame/subscribe [::add-and-print-query bank-account-id type]) 
     :on-success [::added-and-printed]
     :on-error [::forms/save-error ::form]}}))

(re-frame/reg-event-fx
 ::saving
 [with-user (forms/in-form ::form)]
 (fn [{:keys [user] {:keys [data]} :db} _]
   {:graphql
    {:token user
     :owns-state {:single ::form}
     :query-obj (if (:id data)
                  @(re-frame/subscribe [::edit-query])
                  @(re-frame/subscribe [::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)])
     :on-error [::forms/save-error ::form]}}))

(re-frame/reg-event-fx
 ::save-requested
 [with-user (forms/in-form ::form)]
 (fn [{:keys [user db]} [_ fwd-event]]
   (if (and (:scheduled-payment (:data db))
            (not (:vendor-autopay? (:data db))))
     {:dispatch 
      [::modal/modal-requested {:title "Scheduled payment date"
                                :body [:div "This vendor isn't set up to be automatically paid. On "
                                       (: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 [{:keys [db]} [_ result]]
   (let [invoice (first (:invoices (:add-and-print-invoice result)))]
     {:dispatch-n [[::updated (assoc invoice :class "live-added") :create]
                   [::checks-printed [invoice] (:pdf-url (:add-and-print-invoice result))]]})))

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



;; VIEWS

(def invoice-form (forms/vertical-form {:can-submit [::can-submit]
                                        :change-event [::changed]
                                        :submit-event [::save-requested [::saving ]]
                                        :id ::form}))

(re-frame/reg-event-fx
 ::mounted
 (fn []
   {::track/register [{:id ::client
                       :subscription [::subs/client]
                       :event-fn (fn [c]

                                   [::maybe-change-client c])}]}))

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

(defn form-content [{:keys [can-change-amount?] :as params}]
  [layouts/side-bar {:on-close   (dispatch-event [::forms/form-closing ::form ])}
   (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form])
         {:keys [form-inline field raw-field error-notification submit-button ]} invoice-form
         can-submit? (boolean @(re-frame/subscribe [::can-submit]))
         status @(re-frame/subscribe [::status/single ::form])
         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))))]
     (with-meta 
       (form-inline (assoc params :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"]

                                            (and (#{: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 @(re-frame/subscribe [::subs/client])
                       (field [:span "Client"
                               [:span.has-text-danger " *"]]
                              [typeahead-v3 {:entities @(re-frame/subscribe [::subs/clients])
                                                 :entity->text :name
                                                 :type "typeahead-v3"
                                                 :auto-focus (if @(re-frame/subscribe [::subs/client]) false true)
                                                 :field [:client]
                                                 :disabled exists?
                                                 :spec ::invoice/client}]))
                     (field [:span "Vendor"
                             [:span.has-text-danger " *"]]
                            [typeahead-v3 {:entities-by-id @(re-frame/subscribe [::subs/vendors-by-id])
                                           :entity-index @(re-frame/subscribe [::subs/searchable-vendors-index])
                                           :entity->text :name
                                           :type "typeahead-v3"
                                           :disabled exists?
                                           :auto-focus (if @(re-frame/subscribe [::subs/client]) true false)
                                           :field [:vendor]}])

                     (field [:span "Date"
                             [:span.has-text-danger " *"]]
                            [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]
                                          :spec ::invoice/date}])

                     (field "Due (optional)"
                            [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 [:due]
                                          :spec ::invoice/due}])

                     [:p.help "Scheduled payment (optional)"]
                     [:div.level
                      [:div.level-left
                       [:div.level-item
                        [:div.control
                         (raw-field
                          [date-picker {:class-name "input"
                                        :class "input"
                                        :disabled (boolean (:schedule-when-due data))
                                        :format-week-number (fn [] "")
                                        :previous-month-button-label ""
                                        :placeholder "mm/dd/yyyy"
                                        :next-month-button-label ""
                                        :next-month-label ""
                                        :type "date"
                                        :field [:scheduled-payment]
                                        :spec ::invoice/scheduled-payment}])]]
                       [:div.level-item [:div.control
                                         (raw-field 
                                          [switch-field {:id "schedule-when-due"
                                                         :field [:schedule-when-due]
                                                   :label "Same as due date"
                                                   :type "checkbox"}])]]]]

                     (field [:span "Invoice #"
                             [:span.has-text-danger " *"]]
                            [:input.input {:type "text"
                                           :field [:invoice-number]
                                           :spec ::invoice/invoice-number}])


                     (field [:span "Total"
                             [:span.has-text-danger " *"]]
                            [money-field {:type "money"
                                          :field [:total]
                                          :disabled (if can-change-amount? "" "disabled")
                                          :min min-total
                                          :spec ::invoice/total
                                          :step "0.01"}])

                     (with-meta 
                       (field nil
                              [expense-accounts-field {:type "expense-accounts"
                                                       :descriptor "expense account"
                                                       :locations (:locations (:client data))
                                                       :max (:total data)
                                                       :client (or (:client data) @(re-frame/subscribe [::subs/client]))
                                                       :field [:expense-accounts]}])
                       {:key (str (:id (:vendor data)))})

                     

                     

                     (error-notification)

                     [: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 number 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

                       (submit-button "Save")]]])
       {:key id}))])


(defn form [p]
  (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])}))
