(ns auto-ap.graphql.invoices
  (:require
   [auto-ap.datomic
    :refer [conn pull-attr pull-many pull-ref random-tempid  audit-transact audit-transact-batch]]
   [auto-ap.datomic.clients :as d-clients]
   [auto-ap.datomic.invoices :as d-invoices]
   [auto-ap.datomic.vendors :as d-vendors]
   [auto-ap.graphql.checks :as gq-checks]
   [auto-ap.logging :as alog]
   [auto-ap.graphql.utils
    :as u
    :refer [<-graphql
            assert-admin
            assert-can-see-client
            assert-failure
            assert-not-locked
            assert-power-user
            attach-tracing-resolvers
            enum->keyword]]
   [auto-ap.rule-matching :as rm]
   [auto-ap.utils :refer [dollars=]]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]
   [com.brunobonacci.mulog :as mu]
   [datomic.api :as dc]
   [auto-ap.solr :as solr]))

(defn ->graphql [invoice user]
  (if (= "admin" (:user/role user))
    (u/->graphql invoice)
    (u/->graphql (if (:invoice/source-url-admin-only invoice)
                   (dissoc invoice :invoice/source-url)
                   invoice))))

(defn get-invoice-page [context args _]

  (let [args (assoc args :id (:id context))
        [invoices invoice-count outstanding total-amount] (-> args
                                                              :filters
                                                              (assoc :clients (:clients context))
                                                              (<-graphql)
                                                              (update  :status enum->keyword "invoice-status")
                                                              (d-invoices/get-graphql))]
    [{:invoices (mapv #(->graphql % (:id context)) invoices)
      :outstanding outstanding
      :total_amount total-amount
      :total invoice-count
      :count (count invoices)
      :start (-> args :filters (:start 0))
      :end (+ (-> args :filters (:start 0)) (count invoices))}]))

(defn get-all-invoices [context args _]
  (assert-admin (:id context))
  (map
   u/->graphql
   (first (d-invoices/get-graphql (assoc (<-graphql args)
                                         :count Integer/MAX_VALUE)))))

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

(defn reject-invoices [context {:keys [invoices]} _]
  (assert-power-user (:id context))
  (let [invoices (pending-invoices invoices)]
    (doseq [i invoices]
      (assert-can-see-client (:id context) (:db/id (:invoice/client (dc/pull (dc/db conn) [{:invoice/client [:db/id]}]  i)))))
    (let [transactions (mapcat (fn [i] [[:db/retractEntity i]])
                               invoices)]
      (audit-transact transactions (:id context))
      invoices)))

(defn approve-invoices [context {:keys [invoices]} _]
  (assert-power-user (:id context))
  (let [invoices (pending-invoices invoices)]
    (doseq [i    invoices
            :let [invoice (dc/pull (dc/db conn) '[{:invoice/client [:db/id]}
                                                  :invoice/date] i)]]
      (assert-can-see-client (:id context) (-> 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}]) invoices)]
      (audit-transact transactions (:id context))
      invoices)))

(defn assert-no-conflicting [{:keys [invoice_number client_id vendor_id]}]
  (when-not vendor_id
    (assert-failure "Please specify a vendor."))
  (when-not invoice_number
    (assert-failure "Please specify an invoice."))
  (when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice_number
                                           :invoice/vendor vendor_id
                                           :invoice/client client_id}))
    (throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number :validation-error (str "Invoice '" invoice_number "' already exists.")}))))

(defn expense-account->entity [{:keys [id account_id amount location]}]
  #:invoice-expense-account {:amount   amount
                             :db/id    (or id (random-tempid))
                             :account  account_id
                             :location location})

(defn add-invoice-transaction [{:keys [total invoice_number scheduled_payment client_id vendor_id date due expense_accounts]}]
  (let [vendor (d-vendors/get-by-id vendor_id)
        account (:vendor/default-account vendor)
        due (or (and (:vendor/terms vendor)
                     (coerce/to-date
                      (time/plus date (time/days (d-vendors/terms-for-client-id vendor client_id)))))
                due
                nil)
        scheduled_payment (or scheduled_payment
                              (and (d-vendors/automatically-paid-for-client-id? vendor client_id)
                                   due)
                              nil)
        _ (when-not (:db/id account)
            (throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id})))]

    [:upsert-invoice {:db/id                       "invoice"
                      :invoice/invoice-number      invoice_number
                      :invoice/client              client_id
                      :invoice/vendor              vendor_id
                      :invoice/import-status       :import-status/imported
                      :invoice/total               total
                      :invoice/outstanding-balance total
                      :invoice/status              :invoice-status/unpaid
                      :invoice/date                (coerce/to-date date)
                      :invoice/expense-accounts    (map expense-account->entity
                                                        expense_accounts)
                      :invoice/due                 (coerce/to-date due)
                      :invoice/scheduled-payment   (coerce/to-date scheduled_payment)}]))

(defn assert-valid-expense-accounts [expense_accounts vendor_id]
  (doseq [expense-account expense_accounts
          :let [account (dc/pull (dc/db conn)
                                 [:db/id
                                  :account/location
                                  {:account/invoice-allowance [:db/ident]}]
                                 (:account_id expense-account))]]
    (when (empty? (:location expense-account))
      (throw (ex-info "Expense account is missing location" {:validation-error "Expense account is missing location"})))

    (when (and (seq (:account/location account))
               (not= (:location expense-account)
                     (:account/location account)))
      (let [err (str "Account uses location '" (:location expense-account) "' but expects '" (:account/location account) "'")]
        (throw (ex-info err
                        {:validation-error err}))))

    (when (and (= :allowance/denied
                  (:db/ident (:account/invoice-allowance account)))
               (not= (pull-ref (dc/db conn) :vendor/default-account vendor_id)
                     (:db/id account)))
      (let [err (str "Account isn't allowed for invoice use.")]
        (throw (ex-info err
                        {:validation-error err}))))

    (when (and (empty? (:account/location account))
               (= "A" (:location expense-account)))
      (let [err (str "Account uses location '" (:location expense-account) "', which is reserved for liabilities, equities, and assets.")]
        (throw (ex-info err
                        {:validation-error err}))))

    (when (nil? (:account_id expense-account))
      (throw (ex-info "Expense account is missing account" {:validation-error "Expense account is missing account"})))

    (when (nil? (:amount expense-account))
      (throw (ex-info "Expense account does not have an amount specified." {:validation-error "Expense account does not have an amount specified."})))))

(defn assert-invoice-amounts-add-up [{:keys [expense_accounts total]}]
  (let [expense-account-total (reduce + 0 (map (fn [x] (:amount x)) expense_accounts))]
    (when-not (dollars= total expense-account-total)
      (let [error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")]
        (throw (ex-info error {:validation-error error}))))))



(defn add-invoice [context {{:keys [expense_accounts client_id vendor_id] :as in} :invoice} _]
  (assert-no-conflicting in)
  (assert-can-see-client (:id context) client_id)

  (assert-not-locked  client_id (:date in))
  (assert-valid-expense-accounts expense_accounts vendor_id)
  (assert-invoice-amounts-add-up in)

  (let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
    (solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"]))
    (-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
        (->graphql (:id context)))))

(defn assert-bank-account-belongs [client-id bank-account-id]
  (when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
    (throw (ex-info (str "Bank account does not belong to client") {:validation-error "Bank account does not belong to client."}))))



(defn add-and-print-invoice [context {{:keys [total client_id vendor_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
  (mu/trace ::validating-invoice [:invoice in]
            (do
              (assert-no-conflicting in)
              (assert-can-see-client (:id context) client_id)
              (assert-bank-account-belongs client_id bank-account-id)
              (assert-not-locked  client_id (:date in))
              (assert-valid-expense-accounts (:expense_accounts in) vendor_id)
              (assert-invoice-amounts-add-up in)))
  (let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
    (mu/trace ::printing-checks
              []
              (-> (gq-checks/print-checks-internal [{:invoice-id (get-in transaction-result [:tempids "invoice"])
                                                     :amount     total}]
                                                   client_id
                                                   bank-account-id
                                                   type
                                                   (:id context)
                                                   (time/now))
                  u/->graphql))))

(defn edit-invoice [context {{:keys [id due invoice_number vendor_id total date expense_accounts scheduled_payment] :as in} :invoice} _]
  (let [invoice (d-invoices/get-by-id id)
        _ (when (seq (d-invoices/find-conflicting {:db/id id
                                                   :invoice/invoice-number invoice_number
                                                   :invoice/vendor (:db/id (:invoice/vendor invoice))
                                                   :invoice/client (:db/id (:invoice/client invoice))}))
            (throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number})))

        paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
        _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
        _ (assert-not-locked (:db/id (:invoice/client invoice)) (:date in))
        _ (assert-valid-expense-accounts expense_accounts vendor_id)
        _ (assert-invoice-amounts-add-up in)

        updated-invoice {:db/id                  id
                         :invoice/invoice-number invoice_number
                         :invoice/date           (coerce/to-date date)

                         :invoice/total               total
                         :invoice/outstanding-balance (- total paid-amount)
                         :invoice/expense-accounts    (map expense-account->entity
                                                           expense_accounts)
                         :invoice/due                 (coerce/to-date due)
                         :invoice/scheduled-payment   (coerce/to-date scheduled_payment)}]
    (audit-transact [[:upsert-invoice updated-invoice]]
                    (:id context))
    (solr/touch-with-ledger id)
    (-> (d-invoices/get-by-id id)
        (->graphql (:id context)))))

(defn void-invoice [context {id :invoice_id} _]
  (let [invoice (d-invoices/get-by-id id)]
    (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
    (assert-not-locked  (:db/id (:invoice/client invoice)) (:invoice/date invoice))
    (audit-transact [[:upsert-invoice {:db/id                       id
                                       :invoice/total               0.0
                                       :invoice/outstanding-balance 0.0
                                       :invoice/status              :invoice-status/voided
                                       :invoice/expense-accounts    (map (fn [ea] {:db/id                          (:db/id ea)
                                                                                   :invoice-expense-account/amount 0.0})
                                                                         (:invoice/expense-accounts invoice))}]]
                    (:id context))

    (-> (d-invoices/get-by-id id) (->graphql (:id context)))))


(defn get-ids-matching-filters [args]
  (let [ids (some-> args
                    :filters
                    (<-graphql)
                    (assoc :clients (:clients args))
                    (update  :status enum->keyword "invoice-status")
                    (assoc :per-page Integer/MAX_VALUE)
                    d-invoices/raw-graphql-ids
                    :ids)
        specific-ids (d-invoices/filter-ids (:ids args))]
    (into (set ids) specific-ids)))

(defn all-ids-not-locked [all-ids]
  (->> all-ids
       (dc/q '[:find ?i
               :in $ [?i ...]
               :where
               [?i :invoice/client ?c]
               [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
               [?i :invoice/date ?d]
               [(>= ?d ?lu)]]
             (dc/db conn))
       (map first)))

(defn void-invoices [context args _]
  (let [_            (assert-admin (:id context))
        args (assoc args :clients (:clients context))
        all-ids (all-ids-not-locked (get-ids-matching-filters args))
        voidable-cash-payments (->> (dc/q '[:find ?p
                                            :in $ [?i ...]
                                            :where [?ip :invoice-payment/invoice ?i]
                                            [?ip :invoice-payment/payment ?p]
                                            [?p :payment/type :payment-type/cash]
                                            [?i :invoice/client ?c]
                                            [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
                                            [?i :invoice/date ?d]
                                            [(>= ?d ?lu)]]
                                          (dc/db conn)
                                          all-ids)
                                    (map first))]
    (alog/info ::void-payments :count (count voidable-cash-payments))
    (gq-checks/void-payments-internal voidable-cash-payments (:id context))

    (alog/info ::voiding-invoices :count (count all-ids))
    (audit-transact
     (->> all-ids
          (dc/q '[:find (pull ?i [:db/id :invoice/date {:invoice/expense-accounts [:db/id]}])
                  :in $ [?i ...]
                  :where (not [_ :invoice-payment/invoice ?i])
                  [?i :invoice/client ?c]
                  [(get-else $ ?c :client/locked-until #inst "2000-01-01") ?lu]
                  [?i :invoice/date ?d]
                  [(>= ?d ?lu)]]
                (dc/db conn))
          (map
           (fn [[i]]
             [:upsert-invoice {:db/id                       (:db/id i)
                               :invoice/total               0.0
                               :invoice/outstanding-balance 0.0
                               :invoice/status              :invoice-status/voided
                               :invoice/expense-accounts    (mapv
                                                             (fn [iea]
                                                               {:db/id                          (:db/id iea)
                                                                :invoice-expense-account/amount 0.0})
                                                             (:invoice/expense-accounts i))}])))
     (:id context))
    {:message (str "Succesfully voided " (count all-ids))}))

(defn unvoid-invoice [context {id :invoice_id} _]
  (let [invoice (d-invoices/get-by-id id)
        _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
        _ (assert-not-locked  (:db/id (:invoice/client invoice)) (:invoice/date invoice))
        history (dc/history (dc/db conn))
        txs (dc/q {:find  ['?tx '?e '?original-status '?original-outstanding '?total '?ea '?ea-amount]
                   :where ['[?e :invoice/status :invoice-status/voided ?tx true]
                           '[?e :invoice/status ?original-status ?tx false]
                           '[?e :invoice/outstanding-balance ?original-outstanding ?tx false]
                           '[?e :invoice/total ?total ?tx false]
                           '[?ea :invoice-expense-account/amount ?ea-amount ?tx false]]
                   :in    ['$ '?e]}
                  history id)
        [last-transaction] (->> txs (sort-by first) (last))
        tx [[:upsert-invoice
             (->> txs
                  (filter (fn [[tx]] (= tx last-transaction)))
                  (reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]]
                            (-> new-transaction
                                (assoc :db/id entity
                                       :invoice/total total
                                       :invoice/status original-status
                                       :invoice/outstanding-balance original-outstanding)
                                (update :invoice/expense-accounts (fnil conj []) {:db/id expense-account :invoice-expense-account/amount expense-account-amount})))
                          {}))]]]
    (alog/info ::unvoiding-invoice :transaction :tx)
    (audit-transact tx (:id context))

    (-> (d-invoices/get-by-id id)
        (->graphql (:id context)))))

(defn unautopay-invoice [context {id :invoice_id} _]
  (let [invoice (dc/pull (dc/db conn) [{:invoice/client [:db/id]}
                                       :invoice-payment/_invoice
                                       :invoice/total
                                       :invoice/scheduled-payment
                                       :invoice/date] id)]
    (assert (:invoice/client invoice))
    (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
    (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
    (assert (not (seq (:invoice-payment/_invoice invoice))))
    (audit-transact [[:upsert-invoice
                      {:db/id                       id
                       :invoice/status              :invoice-status/unpaid
                       :invoice/outstanding-balance (:invoice/total invoice)
                       :invoice/scheduled-payment   nil}]]
                    (:id context))

    (-> (d-invoices/get-by-id id)
        (->graphql (:id context)))))

(defn edit-expense-accounts [context args _]
  (assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
  (let [invoice-id (:invoice_id args)
        invoice    (d-invoices/get-by-id invoice-id)
        _          (assert-not-locked (:db/id (:invoice/client invoice)) (:invoice/date invoice))
        _          (assert-valid-expense-accounts (:expense_accounts args) (:db/id (:invoice/vendor invoice)))]

    (audit-transact [[:upsert-invoice {:db/id                    invoice-id
                                       :invoice/expense-accounts (map
                                                                  expense-account->entity
                                                                  (:expense_accounts args))}]]
                    (:id context))
    (->graphql
     (d-invoices/get-by-id (:invoice_id args))
     (:id context))))

;; TODO - multiple versions of this now exist. fix in datomic migration?
;; Approach could be something like this: (thanks chatgpt)
;; (defn distribute-cents [amount categories]
;; (let [dollars            (quot amount 1)
;;      cents              (rem amount 1)
;;      cents-per-category (quot cents (count categories))
;;      extra-cents        (rem cents (count categories))]
;;  (zipmap categories (repeat cents-per-category))
;;  (reduce-kv (fn [acc i category]
;;               (update acc category + (if (< i extra-cents) 1 0)))
;;             acc
;;             categories)))
;; convert to/from decimal first
;; for categories, pass in the accounts, and return a tuple of [account amount]
;; after return, just use (assoc account :invoice-expnese-account/amount amount)
(defn maybe-code-accounts [invoice account-rules valid-locations]
  (with-precision 2
    (let [accounts (vec (mapcat
                         (fn [ar]
                           (let [cents-to-distribute (int (Math/round (Math/abs (* (:percentage ar)
                                                                                   (:invoice/total invoice)
                                                                                   100))))]
                             (if (= "Shared" (:location ar))
                               (do
                                 (->> valid-locations
                                      (map
                                       (fn [cents location]
                                         {:db/id (random-tempid)
                                          :invoice-expense-account/account  (:account_id ar)
                                          :invoice-expense-account/amount   (* 0.01 cents)
                                          :invoice-expense-account/location location})
                                       (rm/spread-cents cents-to-distribute (count valid-locations)))))
                               [(cond-> {:db/id (random-tempid)
                                         :invoice-expense-account/account (:account_id ar)
                                         :invoice-expense-account/amount (* 0.01 cents-to-distribute)}
                                  (:location ar) (assoc :invoice-expense-account/location (:location ar)))])))
                         account-rules))
          accounts (mapv
                    (fn [a]
                      (update a :invoice-expense-account/amount
                              #(with-precision 2
                                 (double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
                    accounts)
          leftover (with-precision 2 (.round (bigdec (- (Math/abs (:invoice/total invoice))
                                                        (Math/abs (reduce + 0.0 (map #(:invoice-expense-account/amount %) accounts)))))
                                             *math-context*))
          accounts (if (seq accounts)
                     (update-in accounts [(dec (count accounts)) :invoice-expense-account/amount] #(+ % (double leftover)))
                     [])]
      accounts)))



(defn bulk-change-invoices [context args _]
  (assert-admin (:id context))
  (when-not (:client_id args)
    (throw (ex-info "Client is required"
                    {:validation-error "client is required"})))
  (let [args    (assoc args :clients [{:db/id (:client_id args)}])
        locations (pull-attr (dc/db conn)
                             :client/locations
                             (:client_id args))
        all-ids (all-ids-not-locked (get-ids-matching-filters args))
        invoices (pull-many (dc/db conn) '[:db/id :invoice/total] (vec all-ids))
        account-total (reduce + 0 (map (fn [x] (:percentage x)) (:accounts args)))]
    (when
     (not (dollars= 1.0 account-total))
      (let [error (str "Account total (" account-total ") does not reach 100%")]
        (throw (ex-info error {:validation-error error}))))
    (doseq [a    (:accounts args)
            :let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))]]
      (when (and location (not= location (:location a)))
        (let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
          (throw (ex-info err {:validation-error err}))))
      (when (and (not location)
                 (not (get (into #{"Shared"} locations)
                           (:location a))))
        (let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
          (throw (ex-info err {:validation-error err})))))
    (alog/info ::bulk-code :count (count all-ids))
    (audit-transact-batch
     (map (fn [i]
            [:upsert-invoice {:db/id                    (:db/id i)
                              :invoice/expense-accounts (maybe-code-accounts i (:accounts args) locations)}])
          invoices)
     (:id context))
    {:message (str "Successfully coded " (count all-ids) " invoices.")}))

(def objects
  {:invoice
   {:fields {:id                    {:type :id}
             :original_id           {:type 'Int}
             :client_identifier     {:type 'String}
             :total                 {:type 'String}
             :source_url            {:type 'String}
             :outstanding_balance   {:type 'String}
             :invoice_number        {:type 'String}
             :status                {:type 'String}
             :expense_accounts      {:type '(list :invoices_expense_accounts)}
             :similarity            {:type 'Float}
             :date                  {:type :iso_date}
             :due                   {:type :iso_date}
             :client_id             {:type 'Int}
             :payments              {:type '(list :invoice_payment)}
             :vendor                {:type :vendor}
             :client                {:type :client}
             :scheduled_payment     {:type :iso_date}}}

   :invoices_expense_accounts
   {:fields {:id         {:type :id}
             :invoice_id {:type 'String}
             :account    {:type :account}
             :location   {:type 'String}
             :amount     {:type 'String}}}

   :invoice_page {:fields {:invoices    {:type '(list :invoice)}
                           :outstanding {:type :money}
                           :total_amount {:type :money}
                           :count       {:type 'Int}
                           :total       {:type 'Int}
                           :start       {:type 'Int}
                           :end         {:type 'Int}}}})

(def queries
  {:invoice_page {:type '(list :invoice_page)
                  :args {:filters {:type :invoice_filters}}

                  :resolve :get-invoice-page}
   :all_invoices {:type    '(list :invoice)
                  :args    {:client_id   {:type :id}
                            :client_code {:type 'String}
                            :original_id {:type 'Int}
                            :statuses    {:type '(list String)}}
                  :resolve :get-all-invoices}})

(def mutations
  {:edit_invoice {:type    :invoice
                  :args    {:invoice {:type :edit_invoice}}
                  :resolve :mutation/edit-invoice}
   :add_invoice {:type    :invoice
                 :args    {:invoice {:type :add_invoice}}
                 :resolve :mutation/add-invoice}
   :void_invoice {:type    :invoice
                  :args    {:invoice_id {:type :id}}
                  :resolve :mutation/void-invoice}
   :void_invoices {:type    :message
                   :args    {:filters {:type :invoice_filters}
                             :ids     {:type '(list :id)}}
                   :resolve :mutation/void-invoices}
   :unvoid_invoice {:type    :invoice
                    :args    {:invoice_id {:type :id}}
                    :resolve :mutation/unvoid-invoice}
   :unautopay_invoice {:type    :invoice
                       :args    {:invoice_id {:type :id}}
                       :resolve :mutation/unautopay-invoice}
   :reject_invoices {:type    '(list :id)
                     :args    {:invoices {:type '(list :id)}}
                     :resolve :mutation/reject-invoices}

   :approve_invoices {:type    '(list :id)
                      :args    {:invoices {:type '(list :id)}}
                      :resolve :mutation/approve-invoices}
   :add_and_print_invoice {:type    :check_result
                           :args    {:invoice         {:type :add_invoice}
                                     :bank_account_id {:type :id}
                                     :type            {:type :payment_type}}
                           :resolve :mutation/add-and-print-invoice}
   :edit_expense_accounts {:type    :invoice
                           :args    {:invoice_id       {:type :id}
                                     :expense_accounts {:type '(list :edit_expense_account)}}
                           :resolve :mutation/edit-expense-accounts}

   :bulk_change_invoices {:type    :message
                          :args    {:filters {:type :invoice_filters}
                                    :client_id {:type :id}
                                    :accounts {:type '(list :edit_percentage_account)}
                                    :ids     {:type '(list :id)}}
                          :resolve :mutation/bulk-change-invoices}})

(def input-objects
  {:add_invoice
   {:fields {:id                {:type :id}
             :invoice_number    {:type 'String}
             :expense_accounts  {:type '(list :edit_expense_account)}
             :location          {:type 'String}
             :scheduled_payment {:type :iso_date}
             :date              {:type :iso_date}
             :due               {:type :iso_date}
             :client_id         {:type :id}
             :vendor_id         {:type :id}
             :vendor_name       {:type 'String}
             :total             {:type :money}}}
   :edit_invoice
   {:fields {:id                {:type :id}
             :invoice_number    {:type 'String}
             :expense_accounts  {:type '(list :edit_expense_account)}
             :date              {:type :iso_date}
             :scheduled_payment {:type :iso_date}
             :due               {:type :iso_date}
             :total             {:type :money}}}

   :edit_expense_account
   {:fields {:id         {:type :id}
             :account_id {:type :id}
             :location   {:type 'String}
             :amount     {:type :money}}}

   :invoice_filters {:fields {:import_status       {:type 'String}
                              :exact_match_id      {:type :id}
                              :date_range          {:type :date_range}
                              :due_range           {:type :date_range}
                              :status              {:type :invoice_status}
                              :unresolved          {:type 'Boolean}
                              :scheduled_payments  {:type 'Boolean}
                              :vendor_id           {:type :id}
                              :account_id          {:type :id}
                              :amount_lte          {:type :money}
                              :amount_gte          {:type :money}
                              :invoice_number_like {:type 'String}
                              :location            {:type 'String}
                              :start               {:type 'Int}
                              :per_page            {:type 'Int}
                              :sort                {:type '(list :sort_item)}}}})

(def enums
  {:invoice_status {:values [{:enum-value :paid}
                             {:enum-value :unpaid}
                             {:enum-value :voided}]}})

(def resolvers
  {:get-invoice-page get-invoice-page
   :get-all-invoices get-all-invoices
   :mutation/reject-invoices reject-invoices
   :mutation/approve-invoices approve-invoices
   :mutation/add-invoice add-invoice
   :mutation/add-and-print-invoice add-and-print-invoice
   :mutation/edit-invoice edit-invoice
   :mutation/void-invoice void-invoice
   :mutation/void-invoices void-invoices
   :mutation/bulk-change-invoices bulk-change-invoices
   :mutation/unvoid-invoice unvoid-invoice
   :mutation/unautopay-invoice unautopay-invoice
   :mutation/edit-expense-accounts edit-expense-accounts})

(defn attach [schema]
  (->
   (merge-with merge schema
               {:objects       objects
                :queries       queries
                :mutations     mutations
                :input-objects input-objects
                :enums         enums})
   (attach-tracing-resolvers resolvers)))
