(ns auto-ap.views.pages.transactions.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.views.components :as com]
   [auto-ap.views.components.expense-accounts-field
    :as expense-accounts-field
    :refer [expense-accounts-field-v2]]
   [auto-ap.views.components.layouts :as layouts]
   [auto-ap.views.pages.transactions.common :refer [transaction-read]]
   [auto-ap.views.utils
    :refer [->$ date->str date-picker dispatch-event with-user]]
   [clojure.string :as str]
   [malli.core :as m]
   [re-frame.core :as re-frame]
   [react :as react]
   [reagent.core :as r]
   [vimsical.re-frame.fx.track :as track]))

(def schema
  (m/schema [:map
             [:vendor {:optional true} [:maybe schema/reference]]
             [:accounts expense-accounts-field/schema]
             [:approval-status schema/approval-status]]))

;; SUBS
(re-frame/reg-sub
 ::submit-query
 :<- [::forms/form ::form]
 (fn [{{:keys [id vendor accounts approval-status forecast-match memo ]} :data}]
   {:venia/operation {:operation/type :mutation
                      :operation/name "EditTransaction"}
    :venia/queries [{:query/data
                     [:edit-transaction
                      {:transaction {:id id
                                     :vendor-id (:id vendor)
                                     :memo memo
                                     :approval-status approval-status
                                     :forecast-match (:id forecast-match)
                                     :accounts  (map
                                                 (fn [{:keys [id account amount location]}]
                                                   {:id (when-not (str/starts-with? id "new-")
                                                          id)
                                                    :account-id (:id account)
                                                    :location location
                                                    :amount amount})
                                                 accounts)}}
                      transaction-read]}]}))

;; EVENTS

(re-frame/reg-event-db
 ::editing
 (fn [db [_ which potential-payment-matches potential-autopay-invoices-matches potential-unpaid-invoices-matches potential-transaction-rule-matches]]
   (let [locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client which))])]
     (forms/start-form db ::form
                       (-> which
                           (select-keys [:vendor :amount :payment :client :description-original
                                         :memo
                                         :check-number
                                         :yodlee-merchant :id :potential-payment-matches
                                         :plaid-merchant
                                         :forecast-match :date
                                         :location :accounts :approval-status
                                         :matched-rule])
                           (assoc :original-status (:approval-status which))
                           (assoc :potential-payment-matches potential-payment-matches)
                           (assoc :potential-transaction-rule-matches potential-transaction-rule-matches)
                           (assoc :potential-autopay-invoices-matches potential-autopay-invoices-matches)
                           (assoc :potential-unpaid-invoices-matches potential-unpaid-invoices-matches)
                           (update :accounts expense-accounts-field/from-graphql (:amount 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))])]
      (if (and (= [:vendor-preferences] field)
               value
               (expense-accounts-field/can-replace-with-default? (:accounts data)))
        [[:accounts] (expense-accounts-field/default-account (:accounts data)
                                                             (:default-account (:vendor-preferences data))
                                                             (:amount data)
                                                             locations)]
        [])))))

(re-frame/reg-event-fx
 ::saving
 [with-user (forms/triggers-loading ::form) (forms/in-form ::form)]
 (fn [{:keys [user]} [_]]
   {:graphql
    {:token user
     :query-obj  @(re-frame/subscribe [::submit-query])
     :on-success (fn [result]
                   [::edited (:edit-transaction result)])
     :on-error [::forms/save-error ::form]}}))


(re-frame/reg-event-fx
 ::matching-payment
 [with-user (forms/triggers-loading ::form) (forms/in-form ::form)]
 (fn [{{{:keys [id]} :data} :db user :user} [_ payment-id]]
   {:graphql
    {:token user
     :query-obj {:venia/operation {:operation/type :mutation
                                   :operation/name "MatchTransaction"}
                 :venia/queries [{:query/data [:match-transaction
                                               {:transaction_id id
                                                :payment-id payment-id}
                                               transaction-read]}]}  
     :on-success (fn [result]
                   [::edited (:match-transaction result)])
     :on-error [::forms/save-error ::form]}}))

(re-frame/reg-event-fx
 ::matching-autopay-invoices
 [with-user (forms/triggers-loading ::form) (forms/in-form ::form)]
 (fn [{{{:keys [id]} :data} :db user :user} [_ invoice-ids]]
   {:graphql
    {:token user
     :query-obj {:venia/operation {:operation/type :mutation
                                   :operation/name "MatchTransactionAutopayInvoices"}
                 :venia/queries [{:query/data [:match-transaction-autopay-invoices
                                               {:transaction_id id
                                                :autopay-invoice-ids invoice-ids}
                                               transaction-read]}]}  
     :owns-state {:multi ::matching
                  :which [:autopay-invoices invoice-ids]}
     :on-success (fn [result]
                   [::edited (:match-transaction-autopay-invoices result)])}}))

(re-frame/reg-event-fx
 ::matching-unpaid-invoices
 [with-user (forms/triggers-loading ::form) (forms/in-form ::form)]
 (fn [{{{:keys [id]} :data} :db user :user} [_ invoice-ids]]
   {:graphql
    {:token user
     :query-obj {:venia/operation {:operation/type :mutation
                                   :operation/name "MatchTransactionUnpaidInvoices"}
                 :venia/queries [{:query/data [:match-transaction-unpaid-invoices
                                               {:transaction_id id
                                                :unpaid-invoice-ids invoice-ids}
                                               transaction-read]}]}
     :owns-state {:multi ::matching
                  :which [:unpaid-invoices invoice-ids]}
     :on-success (fn [result]
                   [::edited (:match-transaction-unpaid-invoices result)])}}))

(re-frame/reg-event-fx
 ::matching-rule
 [with-user (forms/triggers-loading ::form) (forms/in-form ::form)]
 (fn [{{{:keys [id]} :data} :db user :user} [_ transaction-rule-id]]
   {:graphql
    {:token user
     :query-obj {:venia/operation {:operation/type :mutation
                                   :operation/name "MatchTransactionRules"}
                 :venia/queries [{:query/data [:match-transaction-rules
                                               {:transaction-ids [id]
                                                :transaction-rule-id transaction-rule-id}
                                               transaction-read]}]}  
     :on-success (fn [result]
                   [::edited (first (:match-transaction-rules result))])
     :on-error [::forms/save-error ::form]}}))

(re-frame/reg-event-fx
 ::unlink
 [with-user (forms/triggers-loading ::form) (forms/in-form ::form)]
 (fn [{{{:keys [id]} :data} :db user :user} [_]]
   {:graphql
    {:token user
     :query-obj {:venia/operation {:operation/type :mutation
                                   :operation/name "UnlinkTransaction"}
                 :venia/queries [{:query/data [:unlink-transaction
                                               {:transaction-id id}
                                               transaction-read]}]}  
     :on-success (fn [result]
                   [::edited (:unlink-transaction result)])
     :on-error [::forms/save-error ::form]}}))

(re-frame/reg-event-fx
 ::edited
 [(forms/triggers-stop ::form)]
 (fn [_ [_ _]]
   {}))


(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]}]})))

(re-frame/reg-event-fx
  ::mounted
  (fn []
    {::track/register {: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 ::vendor-change}}))

;; VIEWS


(defn potential-transaction-rule-matches-box [{:keys [potential-transaction-rule-matches]}]
  (let [states @(re-frame/subscribe [::status/multi ::matching])]
    [:div.box
     [:div.columns
      [:div.column
       [:h1.subtitle.is-5 "Matching Rules:"]]]
     
     [:table.table.compact.is-borderless
      (for [{:keys [note id]} potential-transaction-rule-matches]
        ^{:key id}
        [:tr 
         [:td.no-border note]
         [:td.no-border
          [:a.button.is-primary.is-small {:on-click (dispatch-event [::matching-rule id])
                                          :class (status/class-for (get states [:transaction-rule id]))
                                          :disabled (status/disabled-if-any states)}
           "Use this rule"]]])]]))



(defn potential-payment-matches-box [{:keys [potential-payment-matches]}]
  (let [states @(re-frame/subscribe [::status/multi ::matching])]
    [:div
     [:h1.subtitle.is-5 "Potentially matching payments:"]
     
     [:table.table.compact.is-borderless
      (list
       (for [{:keys [memo check-number vendor id]} potential-payment-matches]
         [:tr 
          [:td.no-border (:name vendor)]
          [:td.no-border (when check-number (str "Check " check-number " ")) memo]
          [:td.no-border
           [:a.button.is-primary.is-small {:on-click (dispatch-event [::matching-payment id])
                                           :class (status/class-for (get states [:payment id]))
                                           :disabled (status/disabled-if-any states)}
            "Match"]]]))]]))

(defn potential-autopay-invoices-matches-box [{:keys [potential-autopay-invoices-matches]}]
  (let [states @(re-frame/subscribe [::status/multi ::matching])]
    [:div
     [:div.notification.is-light.is-info "This transaction may match the following autopay invoice(s)."]
     [:table.table.grid.is-fullwidth
      (list
       (for [invoices potential-autopay-invoices-matches]
         ^{:key (str invoices)}
         [:tr 
          [:td {:style {:width "30%"}} (:name (:vendor (first invoices)))]
          [:td {:style {:overflow "visible" :width "60%"}} [:span.has-tooltip-arrow.has-tooltip-bottom {:data-tooltip (str/join "\n"
                                                                                                                             (for [i invoices]
                                                                                                                               (str (:invoice-number i) " (" (->$ (:total i)) ")"))
                                                                                                                             )}
                                                         (count invoices) " invoices" (if (> (count invoices) 1)
                                                                                        (str " from " (date->str (:date (first invoices))) " - " (date->str (:date (last invoices))))
                                                                                        (str " on " (date->str (:date (first invoices)))))]]
          [:td {:style {:width "6em"}}
           [:a.button.is-primary.is-small {:on-click (dispatch-event [::matching-autopay-invoices (map :id invoices)])
                                           :class (status/class-for (get states [:autopay-invoices (map :id invoices)]))
                                           :disabled (status/disabled-if-any states)}
            "Match"]]]))]]))


(defn potential-unpaid-invoices-matches-box [{:keys [potential-unpaid-invoices-matches]}]
  (let [states @(re-frame/subscribe [::status/multi ::matching])]
    [:div
     [:div.notification.is-light.is-info "This transaction may match the following unpaid invoice(s)."]
     [:table.table.grid.is-fullwidth
      (list
       (for [invoices potential-unpaid-invoices-matches]
         ^{:key (str invoices)}
         [:tr 
          [:td {:style {:width "30%"}} (:name (:vendor (first invoices)))]
          [:td {:style {:overflow "visible" :width "60%"}} [:span.has-tooltip-arrow.has-tooltip-bottom {:data-tooltip (str/join "\n"
                                                                                                                             (for [i invoices]
                                                                                                                               (str (:invoice-number i) " (" (->$ (:total i)) ")"))
                                                                                                                             )}
                                                         (count invoices) " invoices" (if (> (count invoices) 1)
                                                                                        (str " from " (date->str (:date (first invoices))) " - " (date->str (:date (last invoices))))
                                                                                        (str " on " (date->str (:date (first invoices)))))]]
          [:td {:style {:width "6em"}}
           [:a.button.is-primary.is-small {:on-click (dispatch-event [::matching-unpaid-invoices (map :id invoices)])
                                           :class (status/class-for (get states [:unpaid-invoices (map :id invoices)]))
                                           :disabled (status/disabled-if-any states)}
            "Match"]]]))]]))


(defonce ^js/React.Context current-tab-context ( react/createContext "default"))
(def  ^js/React.Provider CurrentTabProvider  (. current-tab-context  -Provider))
(def  ^js/React.Consumer CurrentTabConsumer  (. current-tab-context  -Consumer))

(defn tabs [props & _]
  (let [current-tab (r/atom (:default-tab props))]
    (fn [_ & _]
      (let [current-tab-v @current-tab]
        (r/create-element CurrentTabProvider #js {:value #js {:current-tab current-tab-v
                                                              :on-tab-clicked (fn [new]
                                                                                (reset! current-tab new))}}
                          (r/as-element 
                           [:<>
                            [:div.tabs 
                             (into [:ul]
                                   (r/children (r/current-component)))]
                            (into [:div]
                                  (->> (r/children (r/current-component))
                                       (filter identity)
                                       (filter #(= (:key (second %)) current-tab-v) )
                                       first
                                       (drop 2)))]))))))

(defn tab [props & _]
  [:> CurrentTabConsumer {}
   (fn [consume]
     (let [{:strs [current-tab on-tab-clicked]} (js->clj consume)]
       (r/as-element 
        [:li (when (= (:key props)
                    current-tab)
               {:class "is-active"})
         [:a {:on-click (fn [] (on-tab-clicked (:key props)))} (:title props)]])))])


(defn form-content [_]
  [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form])}
   (let [{:keys [data] } @(re-frame/subscribe [::forms/form ::form])
         locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])
         is-admin? @(re-frame/subscribe [::subs/is-admin?])
         is-power-user? @(re-frame/subscribe [::subs/is-power-user?])
         
         should-disable-for-client? (and (not (or is-admin? is-power-user?))
                                         (not= :requires-feedback (:original-status data)))
         is-already-matched? (:payment data)]
     [form-builder/builder {:change-event [::changed]
                            :submit-event [::saving ]
                            :id           ::form
                            :schema schema}
      [form-builder/section {:title "Transaction"}
       [:<> 

        (when is-admin?

          [form-builder/field-v2 {:field    [:matched-rule :note] }
           "Matched Rule"
           [:input.input {:type     "text" :disabled "disabled"}]])
        [form-builder/field-v2 {:field :amount}
         "Amount"
         [:input.input {:type     "text"
                        :disabled "disabled"}]]
        [form-builder/field-v2 {:field    [:description-original]}
         "Description"
         [:input.input {:type     "text"
                        :disabled "disabled"}]]
        [form-builder/field-v2 {:field [:check-number]}
         "Check Number"
         [:input.input {:type     "text"
                        :disabled "disabled"}]]
        [form-builder/field-v2 {:field [:plaid-merchant :name]}
         "Merchant"
         [:input.input {:type     "text"
                        :disabled "disabled"}]]

        [form-builder/field-v2 {:field [:date]}
         "Date"
         [date-picker {
                       :disabled "disabled"}]]
        
        [form-builder/field-v2 {:field [:memo]}
         "Memo"
         [:input.input {:type "text"}]]


        (when (and (:payment data)
                   (or is-admin? is-power-user?))
          [:p.notification.is-info.is-light>div.level>div.level-left
           [:div.level-item "This transaction is linked to a payment "]
           [:div.level-item [:button.button.is-warning {:on-click (dispatch-event [::unlink])} "Unlink"]]])
        [tabs {:default-tab :details}
         (when
             (and (seq (:potential-transaction-rule-matches data))
                  (not (:matched-rule data))
                  is-admin?)
           [tab {:title "Transaction Rule" :key :transaction-rule}
            [potential-transaction-rule-matches-box {:potential-transaction-rule-matches (:potential-transaction-rule-matches data)}]])
         (when
             (and (seq (:potential-autopay-invoices-matches data))
                  (not is-already-matched?)
                  (or is-admin? is-power-user?))
           [tab  {:title "Autopay Invoices" :key :autopay-invoices}
            [potential-autopay-invoices-matches-box {:potential-autopay-invoices-matches (:potential-autopay-invoices-matches data)}]])

         (when
             (and (seq (:potential-unpaid-invoices-matches data))
                  (not is-already-matched?)
                  (or is-admin? is-power-user?))
           [tab  {:title "Unpaid Invoices" :key :unpaid-invoices}
            [potential-unpaid-invoices-matches-box {:potential-unpaid-invoices-matches (:potential-unpaid-invoices-matches data)}]])
         (when
             (and (seq (:potential-payment-matches data))
                  (not is-already-matched?)
                  (or is-admin? is-power-user?))
           [tab  {:title "Payment" :key :payment}
            [potential-payment-matches-box {:potential-payment-matches (:potential-payment-matches data)}]])

         [tab {:title "Details" :key :details}
          [:div 
           [form-builder/field-v2 {:field :vendor}
            "Vendor"
            [com/search-backed-typeahead {:search-query (fn [i]
                                                          [:search_vendor
                                                           {:query i}
                                                           [:name :id]])
                                          :auto-focus   true
                                          :disabled     (or (boolean (:payment data))
                                                            should-disable-for-client?)}]]
           [form-builder/raw-field-v2 {:field :accounts}
            [expense-accounts-field-v2
             {:max        (Math/abs (js/parseFloat (:amount data)))
              :descriptor "credit account"
              :client     (:client data)
              :disabled   (or (boolean (:payment data))
                              should-disable-for-client?)
              :locations  locations}]]
           [form-builder/field-v2 {:field :approval-status}
            "Approval Status"
            [com/button-radio-input
             {:options  [[:unapproved "Unapproved"]
                         [:requires-feedback "Client Review"]
                         [:approved "Approved"]
                         [:excluded "Excluded from Ledger"]]
              :disabled should-disable-for-client?}]]

           [form-builder/field-v2 {:field [:forecast-match]}
            "Forecasted-transaction"
            [com/entity-typeahead {:entities     @(re-frame/subscribe [::subs/forecasted-transactions-for-client (:id (:client data))])
                                   :entity->text :identifier}]]
           [form-builder/error-notification]
           (when-not should-disable-for-client?
             [form-builder/submit-button "Save"])]]]]]])])

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