(ns auto-ap.routes.exports
  (:require
   [auto-ap.datomic :refer [conn pull-attr pull-many]]
   [auto-ap.datomic.accounts :as accounts]
   [auto-ap.datomic.clients :as d-clients]
   [auto-ap.datomic.transactions :as d-transactions]
   [auto-ap.datomic.vendors :as vendor]
   [auto-ap.graphql :as graphql]
   [auto-ap.graphql.utils
    :refer [->graphql <-graphql assert-admin assert-can-see-client]]
   [auto-ap.logging :as alog]
   [auto-ap.routes.utils :refer [wrap-secure]]
   [auto-ap.ssr.utils :refer [wrap-schema-enforce wrap-form-4xx-2]]
   [auto-ap.time :as atime]
   [buddy.sign.jwt :as jwt]
   [cheshire.generate :as generate]
   [clj-time.coerce :as coerce :refer [to-date]]
   [clj-time.core :as time]
   [clojure.data.csv :as csv]
   [clojure.edn :refer [read-string]]
   [com.unbounce.dogstatsd.core :as statsd]
   [config.core :refer [env]]
   [datomic.api :as dc]
   [malli.core :as mc]
   [ring.middleware.json :refer [wrap-json-response]]
   [venia.core :as venia]))

(defn wrap-csv-response [handler]
  (fn [request]
    (let [response (handler request)]
      (update response :body #(with-open [w (java.io.StringWriter.)]
                                (csv/write-csv w %)
                                (.toString w))))))

(defn aggregated-sales-export [{:keys [query-params]}]
  (let [client-id (Long/parseLong (get query-params "client-id"))
        identity  (jwt/unsign (get query-params "key") (:jwt-secret env) {:alg :hs512})]
    (assert-can-see-client  identity client-id)
    (into (list)
          (dc/q {:find  '[?d4 (sum ?total) (sum ?tax) (sum ?tip)  (sum ?service-charge)]
                 :in    '[$ ?c]
                 :where '[[?s :sales-order/client ?c]
                          [?s :sales-order/date ?d]
                          [?s :sales-order/total ?total]
                          [?s :sales-order/tax ?tax]
                          [?s :sales-order/tip ?tip]
                          [?s :sales-order/service-charge ?service-charge]
                          [(clj-time.coerce/to-date-time ?d) ?d2]
                          [(auto-ap.time/localize ?d2) ?d3]
                          [(auto-ap.time/unparse ?d3 auto-ap.time/normal-date) ?d4]]}
                (dc/db conn) client-id))))

(defn client-tag [params]
  (when-let [code (or (params "client-code")
                      (:client-code params))]
    (str "client:" code)))

(defn datomic-map->graphql-map [m]
  (into {} (map (fn [[k v]]
                  [(keyword (name k))
                   (cond (:db/ident v)
                         (str (keyword (name (:db/ident v))))

                         (map? v)
                         (datomic-map->graphql-map v)

                         (and (seqable? v) (map? (first v)))
                         (into [] (map datomic-map->graphql-map) v)

                         (inst? v)
                         (atime/unparse (clj-time.coerce/to-date-time v) atime/iso-date)

                         (= :db/id k)
                         (str v)

                         (double? v)
                         (str v)


                         :else
                         v)
                   ]))
        m))

(defn export-invoices [{:keys [query-params identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:invoice"}}]
    {:body 
     (list (into (list)
                 (map datomic-map->graphql-map)
                 (map first (dc/q '[:find (pull ?i [:db/id :invoice/total :invoice/outstanding-balance :invoice/invoice-number :invoice/date :invoice/original-id
                                                    { :invoice/status          [:db/ident]
                                                     :invoice/payments
                                                     [:invoice-payment/amount
                                                      {:invoice-payment/payment [:payment/check-number
                                                                                 :payment/memo
                                                                                 {:payment/bank_account [:bank-account/id :bank-account/name :bank-account/number :bank-account/bank-name :bank-account/bank-code :bank-account/code]}]}]
                                                     :invoice/vendor           [:vendor/name
                                                                                :db/id
                                                                                {:vendor/primary-contact [:contact/name]
                                                                                 :vendor/address         [:address/street1 :address/city :address/state :address/zip]}]
                                                     :invoice/expense-accounts [:db/id
                                                                                :invoice-expense-account/amount
                                                                                :invoice-expense-account/id
                                                                                :invoice-expense-account/location
                                                                                {:invoice-expense-account/account
                                                                                 [:db/id :account/numeric-code :account/name]}]
                                                     :invoice/client           [:client/name :db/id :client/code :client/locations]}])
                                    :in $ ?c
                                    :where [?i :invoice/client ?c]]

                                  (dc/db conn)
                                  [:client/code (query-params "client-code")]))))}))

(defn export-payments [{:keys [query-params identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:payment"}}]
    (let [query    [[:all_payments
                     {:client-code (query-params "client-code")
                      :original-id (query-params "original")} 
                  [:id :check-number :amount :memo :date :status :type :original-id
                   [:invoices [[:invoice [:id :original-id]] :amount]]
                   [:bank-account [:number :code :bank-name :bank-code :id]]
                   [:vendor [:name :id [:primary-contact [:name :email :phone]] [:default-account [:name :numeric-code :id]] [:address [:street1 :city :state :zip]]]]
                   [:client [:id :name :code]]
                   ]]]
          payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}) {:clients [ [:client/code (query-params "client-code")]]})]
      {:body 
       (list (:all-payments (:data payments)))})))


(defn export-sales [{:keys [query-params identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:sales"}}]
    (let [query       [[:all_sales_orders
                  (cond-> {:client-code (query-params "client-code")}
                    (query-params "after") (assoc :date-range {:start (query-params "after")
                                                               :end   nil})) 
                  [:id
                   :location 
                   :external_id
                   :total
                   :tip 
                   :tax 
                   :discount
                   :returns
                   :service_charge
                   :date 
                   [:charges [:type_name :total :tip]] 
                   [:line_items [:item_name :total :tax :discount :category]] 
                   [:client [:id :name :code]]]]]
          payments    (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))
          parsedouble #(some-> % Double/parseDouble)  ]
      {:body 
       (seq (map
              (fn [s]
                (-> s
                    (assoc :utc_date (:date s))
                    (update :date (fn [d]
                                    (coerce/to-string (coerce/to-local-date-time (time/to-time-zone (coerce/to-date-time d) (time/time-zone-for-id "America/Los_Angeles"))))))
                    (update :total parsedouble)
                    (update :tax parsedouble)
                    (update :discount parsedouble)
                    (update :tip parsedouble)
                    (update :line-items (fn [lis]
                                          (map
                                            (fn [li]
                                              (-> li
                                                  (update :tax parsedouble)
                                                  (update :discount parsedouble)
                                                  (update :total parsedouble)))
                                            lis)))
                    (update :charges (fn [charges]
                                       (map
                                         (fn [charge]
                                           (-> charge
                                               (update :tip parsedouble)
                                               (update :total parsedouble)))
                                         charges)))))
              (:all-sales-orders (:data payments))))})))

(defn export-expected-deposits [{:keys [query-params identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:deposit"}}]
    (let [query    [[:all_expected_deposits
                  (cond-> {:client-code (query-params "client-code")}
                    (query-params "after") (assoc :date-range {:start (query-params "after")
                                                               :end   nil})) 
                  [:id
                   [:client [:id :name :code]]
                   :location 
                   :external_id
                   :total
                   :fee 
                   :date]]]
          payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
      {:body 
       (seq (map
              (fn [d]
                (-> d
                    (update  :fee #(some-> % Double/parseDouble))
                    (update  :total #(some-> % Double/parseDouble))))
              (:all-expected-deposits (:data payments))))})))


(generate/add-encoder org.joda.time.DateTime
                               (fn [c jsonGenerator]
                                 (.writeString jsonGenerator (str c))))


(defn export-clients[{:keys [identity]}]
  (assert-admin identity)
  {:body (into []
               (map <-graphql)
               (d-clients/get-all))})

(defn export-vendors [{:keys [identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{"export:vendors"}}]
    {:body 
     (map <-graphql (->> (dc/q '[:find ?e
                                :in $
                                :where [?e :vendor/name]]
                              (dc/db conn))
                         (map first)
                         (pull-many (dc/db conn) vendor/default-read)))}))

(defn export-company-vendors [{:keys [identity query-params]}]
  (statsd/time! [(str "export.time") {:tags #{"export:company-vendors"}}]
    (let [client (:db/id (dc/pull (dc/db conn) [:db/id] [:client/code (get query-params "client")]))

          _    (assert-can-see-client  identity client)
          data (->> (dc/q '[:find (pull ?v [:vendor/name
                                           :vendor/terms
                                           {:vendor/default-account   [:account/name :account/numeric-code
                                                                       {:account/client-overrides
                                                                        [:account-client-override/client
                                                                         :account-client-override/name]}]
                                            :vendor/terms-overrides   [:vendor-terms-override/client
                                                                       :vendor-terms-override/terms]
                                            :vendor/account-overrides [:vendor-account-override/client
                                                                       {:vendor-account-override/account [:account/numeric-code :account/name
                                                                                                          {:account/client-overrides
                                                                                                           [:account-client-override/client
                                                                                                            :account-client-override/name]}]}]
                                            :vendor/address           [:address/street1 :address/city :address/state :address/zip]}])
                           :in $ ?c
                           :where [?vu :vendor-usage/client ?c]
                           [?vu :vendor-usage/count ?count]
                           [(>= ?vu 0)]
                           [?vu :vendor-usage/vendor ?v]
                           (not [?v :vendor/hidden true])]
                         (dc/db conn)
                         client)
                    (map (fn [[v]]
                           [(-> v :vendor/name)
                            (-> v :vendor/address :address/street1)
                            (-> v :vendor/address :address/city)
                            (-> v :vendor/address :address/state)
                            (-> v :vendor/address :address/zip)
                            (-> v (vendor/terms-for-client-id client) )
                            (-> v (vendor/account-for-client-id client) (accounts/clientize client) :account/name)
                            (-> v (vendor/account-for-client-id client) :account/numeric-code)
                            ]
                           ))
                    (into [["Vendor Name" "Address" "City" "State" "Zip" "Terms" "Account" "Account Code"]]))]
      {:body 
       (into []
             data)
       :headers {"content-disposition" "attachment; filename=\"vendors.csv\""}})))

(defn export-ledger [{:keys [identity query-params]}]
  (let [start-date (or (some-> (query-params "start-date")
                               (atime/parse atime/iso-date))
                       (time/plus (time/now) (time/days -120)))]
    (alog/info ::exporting-ledger
               :params query-params)
    (assert-admin identity)
    (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                                "export:ledger2"}}]
      (let [results   (->> (dc/q '[:find (pull ?e [:db/id
                                                  :journal-entry/external-id
                                                  :journal-entry/cleared
                                                  :journal-entry/alternate-description
                                                  :journal-entry/date
                                                  :journal-entry/note
                                                  :journal-entry/amount
                                                  :journal-entry/source
                                                  :journal-entry/cleared-against
                                                  :journal-entry/original-entity
                                                  {:journal-entry/client [:client/name :client/code :db/id]
                                                   :journal-entry/vendor [:vendor/name :db/id]
                                                   :journal-entry/line-items
                                                   [:db/id
                                                    :journal-entry-line/location
                                                    :journal-entry-line/debit
                                                    :journal-entry-line/credit
                                                    {:journal-entry-line/account
                                                     [:bank-account/include-in-reports
                                                      :bank-account/bank-name
                                                      :bank-account/numeric-code
                                                      :bank-account/code
                                                      :bank-account/visible
                                                      :bank-account/name
                                                      :bank-account/number
                                                      :account/code
                                                      :account/name
                                                      :account/numeric-code
                                                      :account/location
                                                      {:account/type [:db/ident :db/id]}
                                                      {:bank-account/type [:db/ident :db/id]}]}]}])
                                  :in $ ?c ?start-date
                                  :where [?e :journal-entry/client ?c]
                                  [?e :journal-entry/date ?date]
                                  [(>= ?date ?start-date)]]
                                (dc/db conn)
                                [:client/code (query-params "client-code")]
                                (coerce/to-date start-date)))
            tf-result (transduce (comp
                                   (map first)
                                   (filter (fn [je]
                                             (every?
                                               (fn [jel]
                                                 (let [include-in-reports (-> jel :journal-entry-line/account :bank-account/include-in-reports)]
                                                   (or (nil? include-in-reports)
                                                       (true? include-in-reports))))
                                               (:journal-entry/line-items je))))
                                   (map <-graphql))
                                 conj
                                 (list)
                                 results)]
        {:body 
         tf-result}))))

(defn export-accounts [{:keys [query-params identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:accounts"}}]
    (let [client-id    (d-clients/code->id (query-params "client-code"))
          query        [[:all-accounts
                         [:id :numeric_code :type :applicability :location :name [:client_overrides [:name [:client [:id :code :name]]]]]]]
          all-accounts (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]

      {:body 
       (list (transduce
               (comp
                 (filter (fn [a]
                           (let [overriden-clients (set (map (comp :id :client) (:client-overrides a)))]
                             (or (nil? (:applicability a))
                                 (= :global (:applicability a))
                                 (overriden-clients (str client-id))))))
                 (map (fn [a]
                        (let [client->name (reduce
                                             (fn [override co]
                                               (assoc override (str (:id (:client co))) (:name co)))
                                             {}
                                             (:client-overrides a))]
                          (-> a
                              (assoc :global-name (:name a))
                              (assoc :client-name (client->name (str client-id) (:name a)))
                              (dissoc :client-overrides))))))
               conj
               (list)
               (:all-accounts (:data all-accounts))))})))

(defn export-transactions2 [{:keys [query-params identity]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:transactions2"}}]
    {:body (let [db (dc/db conn)]
             (->> 
               (dc/q {:find  ['?e]
                      :in    ['$ '?client-code]
                      :where ['[?e :transaction/client ?client-code]]}
                     db [:client/code (query-params "client-code")])
               (map first)
               ;; TODO
               #_(map (fn [e]
                      (let [e            (dc/entity db e)
                            client       (:transaction/client e)
                            bank-account (:transaction/bank-account e)]
                        {:id                   (:db/id e)
                         :date                 (:transaction/date e)
                         :post_date            (:transaction/post-date e)
                         :client               { :code (:client/code client)
                                                :id    (:db/id client)
                                                :name  (:client/name client)}
                         :amount               (:transaction/amount e)
                         :description_original (:transaction/description-original e)
                         :approval_status      (:transaction/approval-status e)
                         :bank_account         {:name (:bank-account/name bank-account)
                                                :code (:bank-account/code bank-account)
                                                :id   (:db/id bank-account)}})))))}))

(defn export-transactions [{:keys [query-params identity clients]}]
  (assert-admin identity)
  (statsd/time! [(str "export.time") {:tags #{(client-tag query-params)
                                              "export:transactions"}}]
    (let [[transactions] (d-transactions/get-graphql {:client-code     (query-params "client-code")
                                                      :clients clients
                                                      #_#_:original-id (Integer/parseInt (query-params "original")) 
                                                      :count           Integer/MAX_VALUE})]
      
      
      {:body (map
               (comp ->graphql
                     (fn [i]
                       (cond-> i
                         true                              (update  :transaction/date to-date)
                         true                              (update  :transaction/post-date to-date)
                         (:transaction/payment i)          (update-in [:transaction/payment :payment/date] to-date)
                         (:transaction/expected-deposit i) (update-in [:transaction/expected-deposit :expected-deposit/date] to-date))))
               transactions)})))

(defn export-trial-balance [{{:strs [client-code as-of]} :query-params}]
  (let [db (dc/db conn)
        _ (alog/info ::trial-report-for
                     :client-code client-code
                     :as-of as-of)
        client (pull-attr db :db/id [:client/code client-code])
        as-of (coerce/to-date  (atime/parse as-of atime/iso-date))]

    {:body
     (->> 
         (dc/index-pull db {:index    :avet
                            :selector [:db/id :journal-entry-line/debit :journal-entry-line/location :journal-entry-line/credit {:journal-entry-line/account [:db/id {:account/type [:db/ident]}]} :journal-entry-line/client+account+location+date]
                            :start    [:journal-entry-line/client+account+location+date
                                       [client]]})
         (take-while (fn [jel]
                       (let [[c _ _ _] (:journal-entry-line/client+account+location+date jel)]
                         (= c client))))
         (filter (fn [jel]
                   (let [[_ _ _ d] (:journal-entry-line/client+account+location+date jel)]
                     (<= (compare (or d #inst "2000-01-01") as-of) 0))))
         (reduce
          (fn [acc jel]
            (update acc [(:db/id (:journal-entry-line/account jel)) (:journal-entry-line/location jel)]
                    (fn [v]
                      (if (#{:account-type/asset
                             :account-type/dividend
                             :account-type/expense} (:db/ident (:account/type (:journal-entry-line/account jel))))
                        (update (or v {}) :debit (fnil + 0.0) (or (:journal-entry-line/debit jel) 0.0))
                        (update (or v {}) :credit (fnil + 0.0) (or (:journal-entry-line/credit jel) 0.0))))))
          {})
         (map (fn [[[a l] {:keys [debit credit]}]]
                [(or (pull-attr db :account/name a)
                     (pull-attr db :bank-account/name a))
                 (or (pull-attr db :account/numeric-code a)
                     (pull-attr db :bank-account/numeric-code a))
                 l
                 (or debit 0.0)
                 (or credit 0.0)]))
         (sort-by second))
     :status 200}))

(defn export-raw [{:keys [query-params identity]}]
  (assert-admin identity)
  (alog/info ::executing-query :q (get query-params "query" ))
  (statsd/time! [(str "export.time") {:tags #{"export:raw"}}]
    {:body 
     (into (list) (apply dc/q (read-string (get query-params "query" )) (into [(dc/db conn)] (read-string (get query-params "args" "[]")))))}))

(defn export-ntg-account-snapshot [_]
  (let [clients               (->> (dc/q  '[:find (pull ?e [:db/id :client/code :client/locations])
                                            :in $
                                            :where [?e :client/feature-flags "include-in-ntg-corp-reports"]]
                                          (dc/db conn))
                                   (map first))
        account->numeric-code (into {} (seq (dc/q '[:find ?i ?n
                                                    :in $ [?a ...]
                                                    :where [?i ?a ?n]]
                                                  (dc/db conn) [:account/numeric-code :bank-account/numeric-code])))
        account->name (let [lookup (into {} (seq (dc/q '[:find ?i ?n
                                                         :in $
                                                         :where [?i :account/name ?n]]
                                                       (dc/db conn))))]
                        (fn account->name [x]
                          (or (lookup x) "Bank Account")))
        account->location (into {} (seq (dc/q '[:find ?i ?l
                                                :in $
                                                :where [?i :account/location ?l]]
                                              (dc/db conn))))

        dates (->>   (range -365 0)
                     (map (fn [delta]
                            (atime/unparse-local (time/plus (time/floor (time/plus (atime/local-now) (time/days 1)) time/day) (time/days delta))
                                                 atime/iso-date))))]

    {:status 200
     :body
     (for [client clients
           :let   [all-entries (->> (dc/index-pull (dc/db conn)
                                                   {:index    :avet
                                                    :selector [:db/id :journal-entry-line/running-balance :journal-entry-line/client+account+location+date]
                                                    :start    [:journal-entry-line/client+account+location+date [(:db/id client)]]
                                                    :reverse  false
                                                    :limit    1})
                                    (take-while (fn matches-client [curr]
                                                  (= (first (:journal-entry-line/client+account+location+date curr))
                                                     (:db/id client)))))
                   used-accounts         (->> (for [d    (dc/index-range (dc/db conn)
                                                                         :journal-entry-line/client+account+location+date
                                                                         [(:db/id client)]
                                                                         [(inc (:db/id client))])
                                                    :let [[_ account] (:v d)]]
                                                account)
                                              (into #{})
                                              (sort-by account->numeric-code))
                   _ (alog/info ::account-snapshot-report
                                :used-accounts (count used-accounts)
                                :date-count (count dates))
                   account-lookup (->> all-entries
                                       (reduce
                                        (fn build-account-lookup [acc curr]
                                          (let [[client account location date] (:journal-entry-line/client+account+location+date curr)
                                                numeric-code                   (account->numeric-code account)]
                                            (assoc acc (format "%d-%d-%s-%s" client numeric-code location (atime/unparse-local (clj-time.coerce/to-date-time date) atime/iso-date)) (:journal-entry-line/running-balance curr))))
                                        (sorted-map)))]
           a      (sort-by account->numeric-code used-accounts)
           :let   [last-used-value (atom 0.0)
                   numeric-code (account->numeric-code a)]
           l      (or (account->location a) (:client/locations client))
           :when (and numeric-code (>= numeric-code 40000))
           date-str dates]
       (let [balance (account-lookup (format "%d-%d-%s-%s" (:db/id client) numeric-code l date-str))
             _ (when balance
                 (reset! last-used-value balance))
             balance (or balance @last-used-value)]
         [(:client/code client) l numeric-code (account->name a) date-str
          (format "%.2f" balance)]))}))

(defn export-ntg-sales-snapshot [{:keys [query-params]}]
  (let [clients               (->> (dc/q  '[:find (pull ?e [:db/id :client/code :client/locations])
                                            :in $
                                            :where [?e :client/feature-flags "include-in-ntg-corp-reports"]]
                                          (dc/db conn))
                                   (map first))
        _ (alog/peek query-params)
        date (:date query-params)

        end  (time/plus date (time/days 1))]

    {:status 200
     :body
     (into [["Client" "Date" "Total" "Tip" "Service Charge" "Reference Link" "Tender Total" "Tender Tax" "Tender Tip" "Tender Type" "Tender Processor" "Tender Reference Link" "Line Item Name" "Line Item Category" "Line Item Total"]]
           (for [client clients
                 :let   [all-entries (->> (dc/index-pull (dc/db conn)
                                                         {:index    :avet
                                                          :selector '[:db/id :sales-order/client+date {:sales-order/client [:client/name :client/code]}
                                                                      :sales-order/date
                                                                      :sales-order/total
                                                                      :sales-order/tax
                                                                      :sales-order/tip
                                                                      :sales/reference-link
                                                                      :sales-order/service-charge

                                                                      {:sales-order/charges    [:charge/total :charge/tax :charge/tip
                                                                                             :charge/type-name
                                                                                             :charge/reference-link
                                                                                                {[:charge/processor :xform iol-ion.query/ident] [:db/ident]}]
                                                                       :sales-order/line-items [:order-line-item/item-name
                                                                                                :order-line-item/category
                                                                                                :order-line-item/total]}
                                                                      ]
                                                          :start   [:sales-order/client+date [(:db/id client) (coerce/to-date date)]]
                                                          :end     [:sales-order/client+date [(:db/id client) (coerce/to-date end)]]
                                                          :reverse false
                                                          :limit   100})
                                          (take-while (fn matches-client [curr]
                                                        (and 
                                                          (= (first (:sales-order/client+date curr))
                                                             (:db/id client))
                                                          (< (compare (:sales-order/date curr)
                                                                      (coerce/to-date end))
                                                             0))
                                                        )))
                         ]
                 entry all-entries
                 :let  [sales-columns [(-> entry :sales-order/client :client/name)
                                      (atime/unparse-local (coerce/from-date (-> entry :sales-order/date)) atime/standard-time)
                                      (-> entry :sales-order/total)
                                      (-> entry :sales-order/tip)
                                      (-> entry :sales-order/service-charge)
                                      (-> entry :sales-order/reference-link)
                                      ]
                       sales-column-count (count sales-columns)
                       tender-column-count 6]
                 row   (concat [sales-columns]
                             (map (fn [tender]
                                    (concat 
                                      (take sales-column-count (repeat nil))
                                      [
                                       (-> tender :charge/total)
                                       (-> tender :charge/tax)
                                       (-> tender :charge/tip)
                                       (-> tender :charge/type-name)
                                       (some-> tender :charge/processor name)
                                       (-> tender :charge/reference-link)
                                       ]))
                                  (:sales-order/charges entry))
                             (map (fn [tender]
                                    (concat 
                                      (take (+ sales-column-count tender-column-count) (repeat nil))
                                      [
                                       (-> tender :order-line-item/item-name)
                                       (-> tender :order-line-item/category)
                                       (-> tender :order-line-item/total)
                                       ]))
                                  (:sales-order/line-items entry)))]
             row
             
             #_(let [balance (account-lookup (format "%d-%d-%s-%s" (:db/id client) numeric-code l date-str))
                     _ (when balance
                         (reset! last-used-value balance))
                     balance (or balance @last-used-value)]
                 [ l numeric-code (account->name a) date-str
                  (format "%.2f" balance)])))}))

#_(export-ntg-payment-snapshot nil)

(defn wrap-predetermined-api-key [handler key]
  (fn [request]
    (if (= key (get (:headers request) "authorization"))
      (handler request)
      {:status 401})))


(def routes2 {"api/" {"sales/"            {"aggregated/" {#"export/?" {:get :aggregated-sales-export}}
                                           #"export/?"   {:get :export-sales}
                                           "ntg-export" {:get :export-ntg-sales-snapshot}}
                      "invoices/"         {#"export/?" {:get :export-invoices}}
                      "payments/"         {#"export/?" {:get :export-payments}}
                      "expected-deposit/" {#"export/?" {:get :export-expected-deposits}}

                      "clients/"          {#"export/?" {:get :export-clients}}
                      "vendors/"          {#"export/?" {:get :export-vendors}
                                           "company/"  {#"export" {:get :export-company-vendors}}}
                      "ledger/"           {#"export/?" {:get :export-ledger}
                                           #"trial-balance/export/?" {:get :export-trial-balance}
                                           #"account-snapshot/export" {:get :export-ntg-account-snapshot}}
                      "accounts/"         {#"export/?" {:get :export-accounts}}
                      "transactions/"      {#"export/?" {:get :export-transactions}
                                            #"export2/?" {:get :export-transactions2}}
                      #"raw/?"            {:get :export-raw}}})

(def match->handler {:aggregated-sales-export  (wrap-csv-response aggregated-sales-export)
                     :export-invoices          (-> export-invoices wrap-json-response wrap-secure)
                     :export-payments           (-> export-payments wrap-json-response wrap-secure)
                     :export-sales             (-> export-sales wrap-json-response wrap-secure)
                     :export-expected-deposits (-> export-expected-deposits wrap-json-response wrap-secure)
                     :export-clients           (-> export-clients wrap-json-response wrap-secure)
                     :export-vendors           (-> export-vendors wrap-json-response wrap-secure)
                     :export-company-vendors   (-> export-company-vendors wrap-csv-response wrap-secure)
                     :export-ledger (-> export-ledger wrap-json-response wrap-secure)
                     :export-ntg-account-snapshot (-> export-ntg-account-snapshot wrap-csv-response (wrap-predetermined-api-key "fd07755a-ed4c-4c9a-ad85-fbdd8af37206"))
                     :export-ntg-sales-snapshot (-> export-ntg-sales-snapshot wrap-csv-response
                                                      (wrap-schema-enforce :query-schema (mc/schema [:map
                                                                                                     [:date {:required true
                                                                                                             :decode/string #(try (atime/parse % atime/iso-date) (catch Exception _ nil))} :some]])  )
                                                      (wrap-form-4xx-2 (fn [_] {:body "Invalid Date"}))
                                                      (wrap-predetermined-api-key "fd07755a-ed4c-4c9a-ad85-fbdd8af37206")
                                                      )
                     :export-trial-balance (-> export-trial-balance wrap-csv-response wrap-secure)
                     :export-accounts (-> export-accounts wrap-json-response wrap-secure)
                     :export-transactions (-> export-transactions wrap-json-response wrap-secure)
                     :export-transactions2 (-> export-transactions2 wrap-json-response wrap-secure)
                     :export-raw (-> export-raw wrap-json-response wrap-secure)}) ;
