(ns auto-ap.ssr.invoice.import
  (:require
   [amazonica.aws.s3 :as s3]
   [auto-ap.datomic
             :refer [add-sorter-fields apply-pagination apply-sort-3
                     audit-transact conn merge-query observable-query
                     pull-attr pull-many random-tempid]]
   [auto-ap.datomic.clients :as d-clients]
   [auto-ap.datomic.invoices :as d-invoices]
   [auto-ap.datomic.vendors :as d-vendors]
   [auto-ap.graphql.utils :refer [assert-can-see-client assert-not-locked
                                  can-see-client? exception->notification
                                  extract-client-ids]]
   [auto-ap.logging :as alog]
   [auto-ap.parse :as parse]
   [auto-ap.permissions :refer [can? wrap-must]]
   [auto-ap.routes.invoice :as route]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.ssr.components :as com]
   [auto-ap.ssr.form-cursor :as fc]
   [auto-ap.ssr.grid-page-helper :as helper]
   [auto-ap.ssr.hx :as hx]
   [auto-ap.ssr.invoice.common :refer [default-read]]
   [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 ref->enum-schema strip
                     wrap-entity wrap-implied-route-param wrap-schema-enforce]]
   [auto-ap.time :as atime]
   [auto-ap.utils :refer [dollars=]]
   [bidi.bidi :as bidi]
   [clj-time.coerce :as coerce :refer [to-date]]
   [clojure.java.io :as io]
   [clojure.string :as str]
   [com.brunobonacci.mulog :as mu]
   [config.core :refer [env]]
   [datomic.api :as dc]
   [malli.core :as mc]) 
  (:import
   [java.util UUID]))


(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 filters [request]
  [:form#invoice-filters {"hx-trigger"   "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
                          "hx-get"       (bidi/path-for ssr-routes/only-routes
                                                        ::route/import-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}))
    (date-range-field* request)
    (com/field {:label "Check #"}
               (com/text-input {:name        "check-number"
                                :id          "check-number"
                                :class       "hot-filter"
                                :value       (:check-number (:query-params request))
                                :placeholder "e.g., 10001"
                                :size        :small}))
    (com/field {:label "Invoice #"}
               (com/text-input {:name        "invoice-number"
                                :id          "invoice-number"
                                :class       "hot-filter"
                                :value       (:invoice-number (:query-params request))
                                :placeholder "e.g., ABC-456"
                                :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)
                                          (when (:client-code request)
                                            [:client/code (:client-code request)]))
        query
        (if (:exact-match-id query-params)
          {:query {:find  '[?e]
                   :in    '[$ ?e [?c ...]]
                   :where '[[?e :invoice/client ?c]]}
           :args  [db
                   (:exact-match-id query-params)
                   valid-clients]}
          (cond-> {:query {:find []
                           :in '[$ [?clients ?start ?end]]
                           :where '[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
                                    [?e :invoice/import-status :import-status/pending]]}
                   :args [db
                          [valid-clients
                           (some-> (:start-date query-params) coerce/to-date)
                           (some-> (:end-date query-params) coerce/to-date)]]}



            (:client-id query-params)
            (merge-query {:query {:in    ['?client-id]
                                  :where ['[?e :invoice/client ?client-id]]}
                          :args  [(:client-id query-params)]})

            (:client-code query-params)
            (merge-query {:query {:in    ['?client-code]
                                  :where ['[?e :invoice/client ?client-id]
                                          '[?client-id :client/code ?client-code]]}
                          :args  [(:client-code query-params)]})


            (:start (:due-range query-params)) (merge-query {:query {:in    '[?start-due]
                                                                     :where ['[?e :invoice/due ?due]
                                                                             '[(>= ?due ?start-due)]]}
                                                             :args  [(coerce/to-date (:start (:due-range query-params)))]})

            (:end (:due-range query-params)) (merge-query {:query {:in    '[?end-due]
                                                                   :where ['[?e :invoice/due ?due]
                                                                           '[(<= ?due ?end-due)]]}
                                                           :args  [(coerce/to-date (:end (:due-range query-params)))]})


            (:import-status query-params)
            (merge-query {:query {:in    ['?import-status]
                                  :where ['[?e :invoice/import-status ?import-status]]}
                          :args  [(:import-status query-params)]})
            (:status route-params)
            (merge-query {:query {:in    ['?status]
                                  :where ['[?e :invoice/status ?status]]}
                          :args  [(:status route-params)]})
            (:vendor query-params)
            (merge-query {:query {:in    ['?vendor-id]
                                  :where ['[?e :invoice/vendor ?vendor-id]]}
                          :args  [(:db/id (:vendor query-params))]})

            (:account-id query-params)
            (merge-query {:query {:in    ['?account-id]
                                  :where ['[?e :invoice/expense-accounts ?iea ?]
                                          '[?iea :invoice-expense-account/account ?account-id]]}
                          :args  [(:account-id query-params)]})

            (:amount-gte query-params)
            (merge-query {:query {:in    ['?amount-gte]
                                  :where ['[?e :invoice/total ?total-filter]
                                          '[(>= ?total-filter ?amount-gte)]]}
                          :args  [(:amount-gte query-params)]})

            (:amount-lte query-params)
            (merge-query {:query {:in    ['?amount-lte]
                                  :where ['[?e :invoice/total ?total-filter]
                                          '[(<= ?total-filter ?amount-lte)]]}
                          :args  [(:amount-lte query-params)]})

            (not-empty (:invoice-number query-params))
            (merge-query {:query {:in    ['?invoice-number-like]
                                  :where ['[?e :invoice/invoice-number ?invoice-number]
                                          '[(.contains ^String ?invoice-number ?invoice-number-like)]]}
                          :args  [(:invoice-number query-params)]})

            (:scheduled-payments query-params)
            (merge-query {:query {:in    []
                                  :where ['[?e :invoice/scheduled-payment]]}
                          :args  []})

            (:unresolved query-params)
            (merge-query {:query {:in    []
                                  :where ['(or-join [?e]
                                                    (not [?e :invoice/expense-accounts])
                                                    (and [?e :invoice/expense-accounts ?ea]
                                                         (not [?ea :invoice-expense-account/account])))]}
                          :args  []})

            (seq (:location query-params))
            (merge-query {:query {:in    ['?location]
                                  :where ['[?e :invoice/expense-accounts ?eas]
                                          '[?eas :invoice-expense-account/location ?location]]}
                          :args  [(:location query-params)]})

            (:sort query-params) (add-sorter-fields {"client"               ['[?e :invoice/client ?c]
                                                                             '[?c :client/name ?sort-client]]
                                                     "vendor"               ['[?e :invoice/vendor ?v]
                                                                             '[?v :vendor/name ?sort-vendor]]
                                                     "description-original" ['[?e :transaction/description-original ?sort-description-original]]
                                                     "location"             ['[?e :invoice/expense-accounts ?iea]
                                                                             '[?iea :invoice-expense-account/location ?sort-location]]
                                                     "date"                 ['[?e :invoice/date ?sort-date]]
                                                     "due"                  ['[(get-else $ ?e :invoice/due #inst "2050-01-01") ?sort-due]]
                                                     "invoice-number"       ['[?e :invoice/invoice-number ?sort-invoice-number]]
                                                     "total"                ['[?e :invoice/total ?sort-total]]
                                                     "outstanding-balance"  ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]}
                                                    query-params)
            true
            (merge-query {:query {:find  ['?sort-default '?e]}})))]

    (->> (observable-query query)
         (apply-sort-3 (assoc query-params :default-asc? false))
         (apply-pagination query-params))))


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

(defn sum-outstanding [ids]

  (->>
   (dc/q {:find  ['?id '?o]
          :in    ['$ '[?id ...]]
          :where ['[?id :invoice/outstanding-balance ?o]]}
         (dc/db conn)
         ids)
   (map last)
   (reduce
    +
    0.0)))

(defn sum-total-amount [ids]

  (->>
   (dc/q {:find  ['?id '?o]
          :in    ['$ '[?id ...]]
          :where ['[?id :invoice/total ?o]]}
         (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-outstanding all-ids)
     (sum-total-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]]
                            [:vendor {:optional true :default nil} [:maybe  [:entity-map {:pull [:db/id :vendor/name]}]]]
                            [:check-number {:optional true} [:maybe [:string {:decode/string strip}]]]
                            [:invoice-number {:optional true} [:maybe [:string {:decode/string strip}]]]
                            [:status {:optional true} [:maybe (ref->enum-schema "invoice-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]]]]))

(defn selected->ids [request params]
  (let [all-selected (:all-selected params)
        selected (:selected params)
        ids (cond
              all-selected
              (:ids (fetch-ids (dc/db conn) (-> request
                                                (assoc :query-params params)
                                                (assoc-in [:query-params :start] 0)
                                                (assoc-in [:query-params :per-page]  250))))


              :else
              selected)
        ids (->> (dc/q '[:find ?i
                         :in $ [?i ...]
                         :where [?i :invoice/import-status :import-status/pending]]
                       (dc/db conn)
                       ids)
                 (map first))]
    ids))

(def upload-schema 
  [:map 
   [:force-client {:optional true}
    [:maybe entity-id]]
   [:force-vendor {:optional true}
    [:maybe entity-id]]
[:force-chatgpt {:optional true :default false}
    [:maybe [ :boolean {:decode/string {:enter #(if (= % "on") true

                                                           (boolean %))}}]]]
   [:force-location {:optional true}
    [:maybe [:string {:decode/string strip :min 2 :max 2}]]]])

(defn upload-form [{:keys [form-params form-errors] :as request}]
  (com/content-card {}
                    
                    [:div.px-4.py-3.space-y-4
                     
                     [:div.flex.justify-between.items-center [:h1.text-2xl.mb-3.font-bold "Import new invoices"]
                      
                      #_[:div.flex.gap-2.items-baseline "Trouble with the new upload experience?"
                       [:a {:href (bidi/path-for client-routes/routes :import-invoices)}
                        (com/pill {:color :secondary}
                                  "Go back to previous version")]]]
                     [:div#page-notification.notification.block {:style {:display "none"}}]
                     
                     
                     [:form
                      {:hx-post (bidi/path-for ssr-routes/only-routes
                                               ::route/import-file)
                       :hx-encoding "multipart/form-data"
                       :hx-target "#page-notification"
                       :hx-swap "outerHTML"
                       :id     "upload"}
                      (fc/start-form
                       form-params form-errors
                       [:div.flex.gap-4.items-center
                        
                        (fc/with-field :force-client
                          (com/validated-field {:label "Force client"
                                                :errors (fc/field-errors)}
                                               [:div.w-96
                                                (com/typeahead {:name        (fc/field-name)
                                                                :error?      (fc/error?)
                                                                :class       "w-96"
                                                                :placeholder "Search..."
                                                                :url         (bidi/path-for ssr-routes/only-routes :company-search)
                                                                :value       (fc/field-value)
                                                                :content-fn  (fn [c] (pull-attr (dc/db conn) :client/name c))})]))
                        (fc/with-field :force-location
                          (com/validated-field {:label "Force location"
                                                :errors (fc/field-errors)}
                                               
                                               (com/text-input {:name (fc/field-name)
                                                                :value (fc/field-value)
                                                                :size 2})))
                        (fc/with-field :force-vendor
                          (com/validated-field {:label "Force vendor"
                                                :errors (fc/field-errors)}
                                               [:div.w-96
                                                (com/typeahead {:name        (fc/field-name)
                                                                :error?      (fc/error?)
                                                                :class       "w-96"
                                                                :placeholder "Search..."
                                                                :url         (bidi/path-for ssr-routes/only-routes :vendor-search)
                                                                :value       (fc/field-value)
                                                                :content-fn  (fn [c] (pull-attr (dc/db conn) :vendor/name c))})]))
                        (fc/with-field :force-chatgpt
                          (com/validated-field { :errors (fc/field-errors)
                                                :label " "}
                                               (com/checkbox {:name        (fc/field-name)
                                                              :error?      (fc/error?) }
                                                             "Only use ChatGPT")))])
                      
                      [:div.border-2.border-dashed.rounded-lg.p-4.w-full.text-center.cursor-pointer.h-64.flex.items-center.justify-center.text-lg.relative
                       { :x-data (hx/json {"files" nil
                                           "hovering" false})
                        ":class" "{'bg-blue-100': !hovering,
                                   'border-blue-300': !hovering,
                                   'text-blue-700': !hovering,
                                   'bg-green-100': hovering,
                                   'border-green-300': hovering,
                                   'text-green-700': hovering
                                  }"
                        :x-ref "box"}
                       
                       

                       [:input {:type "file"
                                :name "file"
                                :multiple "multiple"
                                :class "absolute inset-0 m-0 p-0 w-full h-full outline-none opacity-0",
                                :x-on:change "files = $event.target.files; console.log($event.target.files);",
                                :x-on:dragover "hovering = true",
                                :x-on:dragleave "hovering = false",
                                :x-on:drop "hovering = false"}]
                       [:div.flex.flex-col.space-2 
                        [:div 
                         [:ul {:x-show "files != null"}
                          [:template {:x-for "f in files" }
                           [:li (com/pill {:color :primary :x-text "f.name"}) ]
                           ]]]
                        
                        [:div.htmx-indicator-hidden "Drop files to upload here"]]]
                      (com/button {:color :primary :class "w-32 mt-3"} "Upload")]]))

(def grid-page
  (helper/build {:id                 "entity-table"
                 :nav                com/main-aside-nav
                 :check-boxes? true
                 :page-specific-nav  filters
                 :above-grid upload-form

                 :fetch-page         fetch-page
                 :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)])
                 :query-schema query-schema
                 :action-buttons (fn [request]
                                   (let [[_ _ outstanding total] (:page-results request)]
                                     [
                                      (when (can? (:identity request) {:subject :invoice :activity :import})
                                        (com/button {:hx-put    (str (bidi/path-for ssr-routes/only-routes ::route/bulk-approve))
                                                     "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
                                                     "hx-include" "#invoice-filters"
                                                     :color     :primary
                                                     ":disabled" "$data.all_selected || ($data.selected && $data.selected.length > 0)  ? false:  true "
                                                     }
                                                    "Approve selected"))
                                      (when (can? (:identity request) {:subject :invoice :activity :import})
                                        (com/button {:hx-delete    (str (bidi/path-for ssr-routes/only-routes ::route/bulk-disapprove))
                                                     "x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
                                                     "x-init" ""
                                                     "hx-include" "#invoice-filters"
                                                     ":disabled" "$data.all_selected || ($data.selected && $data.selected.length > 0)  ? false:  true "
                                                     :color     :red}
                                                    "Disapprove selected"))]))
                 :row-buttons (fn [request entity]
                                [(when (and (= :import-status/pending (:invoice/import-status entity))
                                            (can? (:identity request) {:subject :invoice :activity :import}))
                                   (com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
                                                                               ::route/disapprove
                                                                               :db/id (:db/id entity))
                                                     :hx-confirm "Are you sure you want to remove this invoice?"}
                                                    svg/thumbs-down))
                                 (when (and (= :import-status/pending (:invoice/import-status entity))
                                            (can? (:identity request) {:subject :invoice :activity :import}))
                                   (com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
                                                                               ::route/approve
                                                                               :db/id (:db/id entity))}
                                                    svg/thumbs-up))])

                 :breadcrumbs        [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
                                       "Invoices"]]
                 :title       (fn [r]
                                (str
                                 (some-> r :route-params :status name str/capitalize (str " "))
                                 "Invoices"))
                 :entity-name "invoices"
                 :route       ::route/import-table
                 :headers     [{: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 :invoice/client :client/name)
                                                   (com/pill {:color :primary} (-> x :invoice/location))])}
                               {:key      "uploader"
                                :name     "Uploaded by"
                                :sort-key "uploader"
                                :render   #(-> % :invoice/uploader :user/name)}
                               {:key      "vendor"
                                :name     "Vendor"
                                :sort-key "vendor"
                                :render   #(-> % :invoice/vendor :vendor/name)}
                               {:key      "invoice-number"
                                :name     "Invoice number"
                                :sort-key "invoice-number"
                                :render   :invoice/invoice-number}
                               {:key           "date"
                                :sort-key "date"
                                :name          "Date"
                                :show-starting "lg"
                                :render        (fn [{:invoice/keys [date]}]
                                                 (some-> date (atime/unparse-local atime/normal-date)))}
                               {:key           "outstanding"
                                :name          "Outstanding"
                                :sort-key "outstanding-balance"
                                :class "text-right"
                                :render        (fn [{:invoice/keys [outstanding-balance total]}]
                                                 [:div
                                                  (some->> outstanding-balance (format "$%,.2f"))
                                                  (when-not (dollars= outstanding-balance total)
                                                    [:div.text-xs.text-gray-400 (format "of $%,.2f" total)])])}]}))

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

(defn disapprove [{invoice :entity :as request identity :identity}]
  (when-not (= :import-status/pending (:invoice/import-status invoice))
    (throw (ex-info  (str "Cannot disapprove an invoice if it is not pending." (:invoice/import-status invoice)) 
                     {:type :notification})))
  (exception->notification
   #(assert-can-see-client identity (:db/id (:invoice/client invoice))))
  
  (audit-transact [[:db/retractEntity (:db/id invoice)]] identity)

  (html-response (row* (:identity request) invoice
                       {:class "live-removed"})
                 :headers {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id invoice))}))

(defn approve [{invoice :entity :as request identity :identity}]
  (when-not (= :import-status/pending (:invoice/import-status invoice))
    (throw (ex-info  (str "Cannot approve an invoice if it is not pending." (:invoice/import-status invoice)) 
                     {:type :notification})))
  (exception->notification
   #(do (assert-can-see-client identity (:db/id (:invoice/client invoice)))
        (assert-not-locked (-> invoice :invoice/client :db/id) (-> invoice :invoice/date))))
  
  (audit-transact [ [:upsert-invoice {:db/id (:db/id invoice) :invoice/import-status :import-status/imported}]] identity)

  (html-response (row* (:identity request) invoice
                       {:class "live-added"})
                 :headers {"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id invoice))}))

(defn bulk-disapprove [request]
  (let [ids (selected->ids request (:form-params request))
        updates (map 
                        (fn [i] [:db/retractEntity i])
                       ids) ]
    (audit-transact updates (:identity request) )

    (html-response [:div]
                   :headers {"hx-trigger" (hx/json { :notification (format "Successfully disapproved %d invoices."
                                                                          (count ids))
                                                    :invalidated "invalidated"})})))

(defn bulk-approve [request]
  (let [ids (selected->ids request (:form-params request))]
    (exception->notification 
     #(doseq [i    ids
             :let [invoice (dc/pull (dc/db conn) '[{:invoice/client [:db/id]}
                                                   :invoice/date] i)]]
       (assert-can-see-client (:identity request) (-> invoice :invoice/client :db/id))
       (assert-not-locked (-> invoice :invoice/client :db/id) (-> invoice :invoice/date))))
    (let [transactions (map (fn [i] [:upsert-invoice {:db/id i :invoice/import-status :import-status/imported}]) ids)]
      (audit-transact transactions (:identity request)))
    (html-response [:div]
                   :headers {"hx-trigger" (hx/json { :notification (format "Successfully approved %d invoices."
                                                                          (count ids))
                                                    :invalidated "invalidated"})})))

#_(defn upload-invoices [{{files      :file
                         files-2    "file"
                         client     :client
                         client-2   "client"
                         location   :location
                         location-2 "location"
                         vendor     :vendor
                         vendor-2   "vendor"} :params
                        user                  :identity}]
  (let [files                       (or files files-2)
        client                      (or client client-2)
        location                    (or location location-2)
        vendor                      (some-> (or vendor vendor-2)
                                            (Long/parseLong))
        ]
    ))

(defn match-vendor [vendor-code forced-vendor vendor-search]
  (when (and (not forced-vendor) (str/blank? vendor-code))
    (if vendor-search 
      (throw (ex-info (format "No vendor found. Searched for '%s'. Please supply an forced vendor."
                              vendor-search)
                      {:vendor-code vendor-code}))
      (throw (ex-info (str "No vendor found. Please supply an forced vendor.")
                      {:vendor-code vendor-code}))))
  (let [vendor-id (or forced-vendor
                      (->> (dc/q
                             {:find  ['?vendor]
                              :in    ['$ '?vendor-name]
                              :where ['[?vendor :vendor/name ?vendor-name]]}
                             (dc/db conn) vendor-code)
                           first
                           first))]
    (when-not vendor-id
      (throw (ex-info (str "Vendor matching name \"" vendor-code "\" not found.")
                      {:vendor-code vendor-code})))

    (if-let [matching-vendor (->> (dc/q
                                    {:find [(list 'pull '?vendor-id d-vendors/default-read)]
                                     :in   ['$ '?vendor-id]}
                                    (dc/db conn) vendor-id)
                                  first
                                  first)]
      matching-vendor
      (throw (ex-info (str "No vendor with the name " vendor-code " was found.")
                      {:vendor-code vendor-code})))))

(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-search vendor-override location-override import-status]} user]
  (when-not total
    (throw (Exception. "Couldn't parse total from file.")))
(when-not date
    (throw (Exception. "Couldn't parse date from file.")))
  (let [matching-client   (cond
                            client-override     client-override
                            account-number      (:db/id (d-clients/exact-match account-number))
                            customer-identifier (:db/id (d-clients/best-match customer-identifier)))
        _ (alog/info ::client-matched
                     :account-number account-number
                     :customer-identifier customer-identifier
                     :client-override client-override
                     :matching (when matching-client
                                 (dc/pull (dc/db conn) [:client/name :client/code] matching-client)))
  
        matching-vendor   (match-vendor vendor-code vendor-override vendor-search)
        
        matching-location (or (when-not (str/blank? location-override)
                                location-override)
                              (parse/best-location-match (dc/pull (dc/db conn)
                                                                  [{:client/location-matches [:location-match/location :location-match/matches]}
                                                                   :client/default-location
                                                                   :client/locations]
                                                                  matching-client)
                                                         text
                                                         full-text))]
    #:invoice {:db/id (random-tempid)
               :invoice/uploader (-> user :db/id)
               :invoice/client              matching-client
               :invoice/client-identifier   (or account-number customer-identifier)
               :invoice/vendor              (:db/id matching-vendor)
               :invoice/source-url          source-url
               :invoice/invoice-number      invoice-number
               :invoice/total               (Double/parseDouble total)
               :invoice/date                (to-date date)
               :invoice/location            matching-location
               :invoice/import-status       (or import-status :import-status/pending)
               :invoice/outstanding-balance (Double/parseDouble total)
               :invoice/status              :invoice-status/unpaid}))

(defn validate-invoice [invoice user]
  (let [missing-keys (for [k [:invoice/invoice-number :invoice/client :invoice/vendor :invoice/total :invoice/outstanding-balance :invoice/date]
                           :when (not (get invoice k))]
                       k
                       )]
    (cond 
     (not (:invoice/client invoice))
      (do 
        (alog/warn ::no-client :invoice invoice)
        (assoc invoice :error-message (str "Searched clients for '" (:invoice/client-identifier invoice) "'. No client found in file. Select a client first.")))

      (not (can-see-client? user (:invoice/client invoice)))
      (do 
        (alog/warn ::cant-see-client :invoice invoice )
        (assoc invoice :error-message "No access for the client in this file.")
        )
      
      (seq missing-keys)
      (do 
        (alog/warn ::mising-keys :keys missing-keys)
        (assoc invoice :error-message (str "Missing the key " missing-keys)))
      :else
      invoice)))

(defn admin-only-if-multiple-clients [is]
  (let [client-count (->> is
                          (map :invoice/client)
                          set
                          count)]
    (map #(assoc % :invoice/source-url-admin-only (boolean (> client-count 1))) is)))


(defn import-uploaded-invoice [user imports]
  (alog/info ::importing-uploaded :count (count imports)
             :bc (or user "NOO"))
  (let [potential-invoices (->> imports
                                (map #(import->invoice % user))
                                (map #(validate-invoice % user))
                                admin-only-if-multiple-clients
                                )
        errored-invoices (->> potential-invoices 
                              (filter #(:error-message %)))
        successful-invoices (->> potential-invoices
                                 (filter #(not (:error-message %))))
        proposed-invoices (->> potential-invoices
                               (filter #(not (:error-message %)))
                               (mapv d-invoices/code-invoice)
                               (mapv (fn [i] [:propose-invoice i])))] 
    
    (alog/info ::creating-invoice :invoices proposed-invoices)
    (let [tx (audit-transact proposed-invoices user)]
      #_(when-not (seq (dc/q '[:find ?i
                             :in $ [?i ...]
                             :where [?i :invoice/invoice-number]]
                           (:db-after tx)
                           (map :e (:tx-data tx))))
        (throw (ex-info "No new invoices found."
                        {:template (:template (first imports))})))
      {:tx tx
       :errored-invoices errored-invoices
       :successful-invoices successful-invoices
       :imports imports})))

(defn import-internal [tempfile filename force-client force-location force-vendor force-chatgpt identity]
  (mu/with-context {:parsing-file filename}
    (try
      (let [extension   (last (str/split (.getName (io/file filename)) #"\."))
            s3-location (str "invoice-files/" (str (UUID/randomUUID)) "." extension)
            _           (s3/put-object  (:data-bucket env)
                                        s3-location
                                        (io/input-stream tempfile)
                                        {:content-type   (if (= "csv" extension)
                                                           "text/csv"
                                                           "application/pdf")
                                         :content-length (.length tempfile)})
            imports     (->> (if force-chatgpt 
                               (parse/glimpse2 (.getPath tempfile))
                               (parse/parse-file (.getPath tempfile) filename :allow-glimpse? true))
                             (map #(assoc %
                                          :client-override force-client
                                          :location-override force-location
                                          :vendor-override force-vendor
                                          :source-url (str "https://" (:data-bucket env)
                                                           "/"
                                                           s3-location))))]
        (try 
          
          (import-uploaded-invoice identity imports)

          (catch Exception e
            (alog/warn ::couldnt-import-upload
                       :error e
                       :template (:template ( first imports)))
            (throw (ex-info (ex-message e)
                            {:template (:template ( first imports))
                             :sample (first imports)}
                            e)))))
      (catch Exception e
        (alog/warn ::couldnt-import-upload
                   :error e)
        (throw e)))))

(defn import-file [request]
  #_(html-response [:div])
  (let [{:keys [file force-client force-vendor force-location force-chatgpt]} (:multipart-params request)
        file (if (vector? file)
               file
               [file])
        results (reduce
                 (fn [result {:keys [filename tempfile]}]
                   (try
                     (let [i (import-internal tempfile filename force-client  force-location force-vendor force-chatgpt (:identity request))]
                       (alog/info ::failure-error-count :count (count (:errored-invoices i)) )
                       
                       (-> result 
                           (update :error? #(or % 
                                                (boolean (seq (:errored-invoices i)))))
                           
                           (update :files conj {:filename filename
                                                :error? (boolean (seq (:errored-invoices i)))
                                                :successful-invoices (count (:successful-invoices i))
                                                :errors [:div 
[:p.text-green-500 [:b (count (:successful-invoices i)) " succeeded in total."]
                                                               ]
                                                         [:p [:b (count (:errored-invoices i)) " failed in total."]
                                                               ]
                                                         [:ul 
                                                          (for [e (take 5 (:errored-invoices i))]
                                                            [:li (:error-message e)]) ]]
                                                :template (:template (first (:imports i)))})))
                     (catch Exception e
                       (-> result
                           (assoc :error? true)
                           (update :files conj {:filename filename
                                                :errors "Can't process file"
                                                :response (.getMessage e)
                                                :sample (:sample (ex-data e))
                                                :template (:template (ex-data e))})))))
                 {:error? false 
                  :files []
                  }
                 file)]
    
    (html-response [:div#page-notification.p-4.rounded-lg
                    [:table
                     [:thead
                      [:tr [:td "File"] [:td "Result"]
                       [:td "Template"]
                       [:td "Sample"]]
                      #_[:tr "Result"]
                      #_[:tr "Template"]]
                     (for [r (:files results)]
                       [:tr
                        [:td.p-2.border.align-top
                         {:class (if (:error? r)
                                   "bg-red-50 text-red-700 border-red-300"
                                   "bg-primary-50 text-primary-700 border-green-500")}
                         (:filename r)]
                        [:td.p-2.border.align-top
                         {:class (if (:error? r)
                                   "bg-red-50 text-red-700 border-red-300"
                                   "bg-primary-50 text-primary-700 border-green-500")}
                         (:errors r)]
                        [:td.p-2.border.align-top
                         {:class (if (:error? r)
                                   "bg-red-50 text-red-700 border-red-300"
                                   "bg-primary-50 text-primary-700 border-green-500")}
                         "Template: " (:template r)]
                        [:td.p-2.border.align-top
                         {:class (if (:error? r)
                                   "bg-red-50 text-red-700 border-red-300"
                                   "bg-primary-50 text-primary-700 border-green-500")}
                         [:ul
                          (for [[k v] (dissoc (:sample r) :template :source-url :full-text :text)]
                            [:li (name k) ": " (str v)])]
                         #_(:template r)]])]]
                   :headers
                   {"hx-trigger" "invalidated"})))

#_(defn wrap-test [handler]
  (fn [request]
    (clojure.pprint/pprint (:multipart-params request))
    (handler request )))

(def key->handler
  (apply-middleware-to-all-handlers 
   {::route/import-page
    (->
     (helper/page-route grid-page)
     (wrap-implied-route-param :status nil))
    ::route/import-table 
    (-> (helper/table-route grid-page)
        (wrap-implied-route-param :status nil))
    
    ::route/disapprove (-> disapprove
                           (wrap-entity [:route-params :db/id] default-read)
                           (wrap-schema-enforce :route-params [:map [:db/id entity-id]]))
    ::route/approve (-> approve
                           (wrap-entity [:route-params :db/id] default-read)
                           (wrap-schema-enforce :route-params [:map [:db/id entity-id]]))
    ::route/bulk-disapprove (-> bulk-disapprove
                                (wrap-schema-enforce :form-schema query-schema))
    ::route/bulk-approve (-> bulk-approve
                                (wrap-schema-enforce :form-schema query-schema))
    ::route/import-file (-> import-file
                            (wrap-schema-enforce :multipart-schema upload-schema))}
   (fn [a]
     (-> a 
         (wrap-must {:subject :invoice :activity :import})))))


