(ns auto-ap.ssr.transaction
  (:require
   [auto-ap.client-routes :as client-routes]
   [auto-ap.datomic
             :refer [add-sorter-fields apply-pagination apply-sort-4 conn
                     merge-query observable-query pull-many]]
   [auto-ap.graphql.utils :refer [extract-client-ids]]
   [auto-ap.permissions :refer [wrap-must]]
   [auto-ap.query-params :refer [wrap-copy-qp-pqp]]
   [auto-ap.routes.transactions :as route]
   [auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated]]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.ssr.components :as com]
   [auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
   [auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
   [auto-ap.ssr.hx :as hx]
   [auto-ap.ssr.ledger :refer [wrap-ensure-bank-account-belongs]]
   [auto-ap.ssr.pos.common :refer [date-range-field*]]
   [auto-ap.ssr.svg :as svg]
   [auto-ap.ssr.utils
             :refer [apply-middleware-to-all-handlers clj-date-schema
                     entity-id html-response strip wrap-merge-prior-hx
                     wrap-schema-enforce]]
   [auto-ap.time :as atime]
   [bidi.bidi :as bidi]
   [clj-time.coerce :as coerce]
   [clojure.string :as str]
   [datomic.api :as dc]
   [hiccup.util :as hu]
   [malli.core :as mc]))

(defn exact-match-id* [request]
  (if (nat-int? (:exact-match-id (:query-params request)))
    [:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag"}
     (com/hidden {:name "exact-match-id"
                  "x-model" "exact_match"})
     (com/pill {:color :primary}
               [:span.inline-flex.space-x-2.items-center
                [:div "exact match"]
                [:div.w-3.h-3
                 (com/link {"@click" "exact_match=null; $nextTick(() => $dispatch('change'))"}
                           svg/x)]])]
    [:div {:id "exact-match-id-tag"}]))

(defn bank-account-filter* [request]
  [:div {:hx-trigger "clientSelected from:body"
         :hx-get (bidi.bidi/path-for ssr-routes/only-routes ::route/bank-account-filter)
         :hx-target "this"
         :hx-swap "outerHTML"}
   (when (:client request)
     (let [bank-account-belongs-to-client? (get (set (map :db/id (:client/bank-accounts (:client request))))
                                                (:db/id (:bank-account (:query-params request))))]
       (com/field {:label "Bank Account"}
                  (com/radio-card {:size    :small
                                   :name    "bank-account"
                                   :value (or (when bank-account-belongs-to-client?
                                                (:db/id (:bank-account (:query-params request))))
                                              "")
                                   :options
                                   (into [{:value ""
                                           :content "All"}]
                                         (for [ba (:client/bank-accounts (:client request))]
                                           {:value   (:db/id ba)
                                            :content (:bank-account/name ba)}))}))))])

(defn bank-account-filter [request]
  (html-response (bank-account-filter* request)))

(defn filters [request]
  [:form#transaction-filters {"hx-trigger"   "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
                              "hx-get"       (bidi/path-for ssr-routes/only-routes
                                                            ::route/table)
                              "hx-target"    "#entity-table"
                              "hx-indicator" "#entity-table"}

   (com/hidden {:name "status"
                :value (some-> (:status (:query-params request)) name)})
   [:fieldset.space-y-6
    (com/field {:label "Vendor"}
               (com/typeahead {:name        "vendor"
                               :id          "vendor"
                               :url (bidi/path-for ssr-routes/only-routes :vendor-search)
                               :value       (:vendor (:query-params request))
                               :value-fn :db/id
                               :content-fn :vendor/name}))
    (bank-account-filter* request)

    (date-range-field* request)

    (com/field {:label "Description"}
               (com/text-input {:name        "description"
                                :id          "description"
                                :class       "hot-filter"
                                :value       (:description (:query-params request))
                                :placeholder "e.g., Groceries"
                                :size        :small}))

    (com/field {:label "Amount"}
               [:div.flex.space-x-4.items-baseline
                (com/money-input {:name        "amount-gte"
                                  :id          "amount-gte"
                                  :hx-preserve "true"
                                  :class       "hot-filter  w-20"
                                  :value       (:amount-gte (:query-params request))
                                  :placeholder "0.01"
                                  :size        :small})
                [:div.align-baseline
                 "to"]
                (com/money-input {:name        "amount-lte"
                                  :hx-preserve "true"
                                  :id          "amount-lte"
                                  :class       "hot-filter w-20"
                                  :value       (:amount-lte (:query-params request))
                                  :placeholder "9999.34"
                                  :size        :small})])
    (exact-match-id* request)]])

(defn fetch-ids [db {:keys [query-params route-params] :as request}]
  (let [valid-clients (extract-client-ids (:clients request)
                                          (:client-id request)
                                          (:client-id query-params)
                                          (when (:client-code request)
                                            [:client/code (:client-code request)]))
        args query-params
        query
        (if (:exact-match-id args)
          {:query {:find  '[?e]
                   :in    '[$ ?e [?c ...]]
                   :where '[[?e :transaction/client ?c]]}
           :args  [db
                   (:exact-match-id args)
                   valid-clients]}
          (cond-> {:query {:find []
                           :in ['$ '[?clients ?start ?end]]
                           :where '[[(iol-ion.query/scan-transactions $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]}
                   :args [db
                          [valid-clients
                           (some-> (:start-date query-params) coerce/to-date)
                           (some-> (:end-date query-params) coerce/to-date)]]}

            (seq (:description args))
            (merge-query {:query {:in    ['?description]
                                   :where ['[?e :transaction/description-original ?do]
                                           '[(clojure.string/lower-case ?do) ?do2]
                                           '[(.contains ?do2 ?description)]]}
                           :args  [(str/lower-case (:description args))]})

            (:amount-gte args)
            (merge-query {:query {:in    ['?amount-gte]
                                  :where ['[?e :transaction/amount ?a]
                                          '[(>= ?a ?amount-gte)]]}
                          :args  [(:amount-gte args)]})

            (:amount-lte args)
            (merge-query {:query {:in    ['?amount-lte]
                                  :where ['[?e :transaction/amount ?a]
                                          '[(<= ?a ?amount-lte)]]}
                          :args  [(:amount-lte args)]})

            (:db/id (:bank-account args))
            (merge-query {:query {:in    ['?ba]
                                  :where ['[?e :transaction/bank-account ?ba]]}
                          :args  [(:db/id (:bank-account args))]})
            
            (:vendor args)
            (merge-query {:query {:in    ['?vendor-id]
                                  :where ['[?e :transaction/vendor ?vendor-id]]}
                          :args  [(:db/id (:vendor args))]})


            (:sort args) (add-sorter-fields {"client"      ['[?e :transaction/client ?c]
                                                            '[?c :client/name ?sort-client]]
                                             "vendor" '[(or-join  [?e ?sort-vendor]
                                                                       (and
                                                                        [?e :transaction/vendor ?v]
                                                                        [?v :vendor/name ?sort-vendor])
                                                                       (and [(missing?  $ ?e :transaction/vendor)]
                                                                            [(ground "") ?sort-vendor]))]
                                             "date"        ['[?e :transaction/date ?sort-date]]
                                             "amount"      ['[?e :transaction/amount ?sort-amount]]
                                             "description" ['[?e :transaction/description-original ?sort-description]]}
                                            args)

            true
            (merge-query {:query {:find ['?sort-default '?e]}})))]

    (->> (observable-query query)
         (apply-sort-4 (assoc query-params :default-asc? true))
         (apply-pagination query-params))))

(def default-read
  '[:transaction/amount
    :transaction/description-original
    :transaction/description-simple
    [ :transaction/date :xform clj-time.coerce/from-date]
    [ :transaction/post-date :xform clj-time.coerce/from-date]
    :transaction/type
    :transaction/status
    :db/id
    {:transaction/vendor [:vendor/name :db/id]
     :transaction/client [:client/name :client/code :db/id]
     :transaction/bank-account [:bank-account/numeric-code :bank-account/name]
     :transaction/accounts [{:transaction-account/account [:account/name :db/id]}
                            :transaction-account/location
                            :transaction-account/amount]
     :transaction/matched-rule [:matched-rule/name]}])

(defn hydrate-results [ids db _]
  (let [results (->> (pull-many db default-read ids)
                     (group-by :db/id))
        results (->> ids
                     (map results)
                     (map first))]
    results))

(defn sum-amount [ids]
  (->>
   (dc/q {:find ['?id '?a]
          :in ['$ '[?id ...]]
          :where ['[?id :transaction/amount ?a]]}
         (dc/db conn)
         ids)
   (map last)
   (reduce + 0.0)))

(defn fetch-page [request]
  (let [db (dc/db conn)
        {ids-to-retrieve :ids matching-count :count
         all-ids :all-ids} (fetch-ids db request)]

    [(->> (hydrate-results ids-to-retrieve db request))
     matching-count
     (sum-amount all-ids)]))

(def query-schema (mc/schema
                   [:maybe [:map {:date-range [:date-range :start-date :end-date]}
                            [:sort {:optional true} [:maybe [:any]]]
                            [:per-page {:optional true :default 25} [:maybe :int]]
                            [:start {:optional true :default 0} [:maybe :int]]
                            [:amount-gte {:optional true} [:maybe :double]]
                            [:amount-lte {:optional true} [:maybe :double]]
                            [:client-id {:optional true} [:maybe entity-id]]
                            [:description {:optional true} [:maybe [:string {:decode/string strip}]]]
                            [:vendor {:optional true :default nil} [:maybe  [:entity-map {:pull [:db/id :vendor/name]}]]]
                            [:bank-account {:optional true :default nil} [:maybe  [:entity-map {:pull [:db/id :bank-account/numeric-code]}]]]
                            #_[:status {:optional true} [:maybe (ref->enum-schema "transaction-status")]]
                            [:exact-match-id {:optional true} [:maybe entity-id]]
                            [:all-selected {:optional true :default nil} [:maybe :boolean]]
                            [:selected {:optional true :default nil} [:maybe [:vector {:coerce? true}
                                                                              entity-id]]]
                            [:start-date {:optional true}
                             [:maybe clj-date-schema]]
                            [:end-date {:optional true}
                             [:maybe clj-date-schema]]]]))


(def grid-page
  (helper/build {:id                 "entity-table"
                 :nav                com/main-aside-nav
                 :check-boxes? true
                 :page-specific-nav  filters
                 :fetch-page         fetch-page
                 :query-schema query-schema
                 :oob-render
                 (fn [request]
                   [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)
                    (assoc-in (exact-match-id* request)  [1 :hx-swap-oob] true)])
                 :action-buttons (fn [request]
                                   [(com/button {:color :primary
                                                 :hx-get (bidi/path-for ssr-routes/only-routes
                                                                        ::route/new)}
                                                "Add Transaction")])
                 :row-buttons (fn [request entity]
                                [#_(when (and (can? (:identity request) {:subject :transaction :activity :edit}))
                                     (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
                                                                              ::route/edit-wizard
                                                                              :db/id (:db/id entity))}
                                                      svg/pencil))
                                 #_(when (and (can? (:identity request) {:subject :transaction :activity :delete}))
                                     (com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
                                                                                 ::route/delete
                                                                                 :db/id (:db/id entity))
                                                       :hx-confirm "Are you sure you want to delete this transaction?"}
                                                      svg/trash))])
  
                 :breadcrumbs        [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
                                       "Transactions"]]
                 :title       (fn [r]
                                "Transaction")
                 :entity-name "register"
                 :route       ::route/table
                 :csv-route   ::route/csv
                 :break-table (fn [request entity]
                                (cond
                                  (= (-> request :query-params :sort first :name) "Vendor")
                                  (or (-> entity :transaction/vendor :vendor/name)
                                      "No vendor")
  
  
  
                                  :else nil))
                 :page->csv-entities (fn [[transactions]]
                                       transactions)
                 :headers     [{:key      "id"
                                :name     "Id"
                                :render-csv :db/id
                                :render-for #{:csv}}
                               {:key      "client"
                                :name     "Client"
                                :sort-key "client"
                                :hide?    (fn [args]
                                            (and (= (count (:clients args)) 1)
                                                 (= 1 (count (:client/locations (:client args))))))
                                :render   (fn [x] [:div.flex.items-center.gap-2 (-> x :transaction/client :client/name)])
                                :render-csv (fn [x] (-> x :transaction/client :client/name))}
                               {:key      "vendor"
                                :name     "Vendor"
                                :sort-key "vendor"
                                :render   (fn [e]
                                            #_(alog/peek :vend e)
                                            (or
                                             (-> e :transaction/vendor :vendor/name)
                                             [:span.italic.text-gray-400 (-> e :transaction/description-simple)]))
                                :render-csv   (fn [e] (or (-> e :transaction/vendor :vendor/name)
                                                          (-> e :transaction/description-simple)))}
                               {:key      "description"
                                :name     "Description"
                                :sort-key "description"
                                :render   :transaction/description-original
                                :render-csv :transaction/description-original}
                               {:key           "date"
                                :sort-key "date"
                                :name          "Date"
                                :show-starting "lg"
                                :render        (fn [{:transaction/keys [date]}]
                                                 (some-> date (atime/unparse-local atime/normal-date)))}
                               {:key           "amount"
                                :name          "Amount"
                                :sort-key "amount"
                                :class "text-right"
                                :render        #(format "$%,.2f" (:transaction/amount %))
                                :render-csv    :transaction/amount}
                               {:key           "links"
                                :name          "Links"
                                :show-starting "lg"
                                :class "w-8"
                                :render        (fn [i]
                                                 (link-dropdown
                                                  (cond-> []
                                                    (:transaction/payment i)
                                                    (conj
                                                     {:link (hu/url (bidi/path-for ssr-routes/only-routes
                                                                                   ::route/payment-page)
                                                                    {:exact-match-id (:db/id (:transaction/payment i))})
                                                      :color :primary
                                                      :content (format "Payment '%s'" (-> i :transaction/payment :payment/invoice-number))})
  
                                                    (and (:transaction/client-overrides i) (seq (:transaction/client-overrides i)))
                                                    {:link (hu/url (bidi/path-for client-routes/routes
                                                                                  :transactions)
                                                                   {:exact-match-id (:db/id i)})
                                                     :color :primary
                                                     :content "Client Overrides"})))
                                :render-for #{:html}}]}))

(def row* (partial helper/row* grid-page))

;; Handlers


(def page (helper/page-route grid-page))

(def table (helper/table-route grid-page))

(def csv (helper/csv-route grid-page))

(def key->handler
  (apply-middleware-to-all-handlers
   {::route/page              page
    ::route/table             table
    ::route/csv               csv
    ::route/bank-account-filter bank-account-filter}
   (fn [h]
     (-> h
         (wrap-copy-qp-pqp)
         (wrap-apply-sort grid-page)
          (wrap-ensure-bank-account-belongs)
          (wrap-merge-prior-hx)
         (wrap-schema-enforce :query-schema query-schema)
         (wrap-schema-enforce :hx-schema query-schema)
         (wrap-must {:activity :view :subject :transaction})
         (wrap-client-redirect-unauthenticated)))))