(ns auto-ap.graphql
  (:require
   [auto-ap.datomic :refer [merge-query uri]]
   [auto-ap.datomic.checks :as d-checks]
   [auto-ap.datomic.sales-orders :as d-sales-orders]
   [auto-ap.datomic.users :as d-users]
   [auto-ap.graphql.accounts :as gq-accounts]
   [auto-ap.graphql.checks :as gq-checks]
   [auto-ap.graphql.clients :as gq-clients]
   [auto-ap.graphql.expected-deposit :as gq-expected-deposit]
   [auto-ap.graphql.import-batch :as gq-import-batches]
   [auto-ap.graphql.intuit-bank-accounts :as gq-intuit-bank-accounts]
   [auto-ap.graphql.invoices :as gq-invoices]
   [auto-ap.graphql.ledger :as gq-ledger]
   [auto-ap.graphql.sales-orders :as gq-sales-orders]
   [auto-ap.graphql.transaction-rules :as gq-transaction-rules]
   [auto-ap.graphql.transactions :as gq-transactions]
   [auto-ap.graphql.users :as gq-users]
   [auto-ap.graphql.utils :refer [assert-admin assert-can-see-client]]
   [auto-ap.graphql.vendors :as gq-vendors]
   [auto-ap.graphql.yodlee-merchants :as ym]
   [auto-ap.graphql.yodlee2 :as gq-yodlee2]
   [auto-ap.logging :refer [error-event info-event warn-event]]
   [auto-ap.time :as time]
   [clj-time.coerce :as coerce]
   [clj-time.core :as t]
   [clojure.string :as str]
   [clojure.tools.logging :as log]
   [clojure.walk :as walk]
   [com.walmartlabs.lacinia :refer [execute]]
   [com.walmartlabs.lacinia.schema :as schema]
   [com.walmartlabs.lacinia.util :refer [attach-resolvers]]
   [datomic.api :as d]
   [unilog.context :as lc]
   [yang.time :refer [time-it]])
  (:import
   (clojure.lang IPersistentMap)))

(def integreat-schema
  {
   :scalars {:id {:parse #(when % (Long/parseLong %))
                  :serialize #(.toString %)}
             :ident {:parse (fn [x] {:db/ident x}) 
                     :serialize #(or (:ident %) (:db/ident %) %)}
             :iso_date {:parse #(time/parse % time/iso-date)
                        :serialize #(time/unparse % time/iso-date)}
             :money {:parse #(cond (and (string? %)
                                        (not (str/blank? %)))
                                   (Double/parseDouble %)

                                   (and (string? %)
                                        (str/blank? (str/trim %)))
                                   0.0

                                   (nil? %)
                                   0.0

                                   (int? %)
                                   (double %)

                                   :else
                                   %)
                     :serialize #(cond (double? %)
                                   (str %)

                                   (int? %)
                                   (str %)

                                   :else
                                   %)}
                     
             :percentage {:parse #(cond (and (string? %)
                                            (not (str/blank? %)))
                                       (Double/parseDouble %)

                                       (int? %)
                                       (double %)

                                       :else
                                       %)
                          :serialize #(if (double? %)
                                        (str %)
                                        %)}}
   :objects
   {
    :message
    {:fields {:message {:type 'String}}}
    :location_match
    {:fields {:location {:type 'String}
              :match {:type 'String}
              :id {:type :id}}}

    :client
    {:fields {:id {:type :id}
              :name {:type 'String}
              :code {:type 'String}
              :signature_file {:type 'String}
              :week_a_debits {:type :money}
              :week_a_credits {:type :money}
              :week_b_debits {:type :money}
              :week_b_credits {:type :money}
              :email {:type 'String}
              :address {:type :address}
              :location_matches {:type '(list :location_match)}
              :locations {:type '(list String)}
              :matches {:type '(list String)}
              :bank_accounts {:type '(list :bank_account)}
              :forecasted_transactions {:type '(list :forecasted_transaction)}
              :yodlee_provider_accounts {:type '(list :yodlee_provider_account)}}}

    :yodlee_provider_account
    {:fields {:id {:type 'Int}
              :client {:type :client}
              :status {:type 'String}
              :detailed_status {:type 'String}
              :last_updated {:type :iso_date}
              :accounts {:type '(list :yodlee_account)}}}
    
    :yodlee_account
    {:fields {:id {:type 'Int}
              :status {:type 'String}
              :available_balance {:type :money}
              :name {:type 'String}
              :number {:type 'String}
              :last_updated {:type :iso_date}}}
    :contact
    {:fields {:id {:type :id}
              :name {:type 'String}
              :email {:type 'String}
              :phone {:type 'String}}}

    :bank_account
    {:fields {:id {:type :id}
              :type {:type :ident}
              :start_date {:type :iso_date}
              :number {:type 'String}
              :numeric_code {:type 'Int}
              :sort_order {:type 'Int}
              :visible {:type 'Boolean}
              :include_in_reports {:type 'Boolean}
              :routing {:type 'String}
              :code {:type 'String}
              :check_number {:type 'Int}
              :name {:type 'String}
              :bank_code {:type 'String}
              :bank_name {:type 'String}
              :current_balance {:type :money}
              :yodlee_balance_old {:type :money}
              :yodlee_account_id {:type 'Int}
              :yodlee_account {:type :yodlee_account}
              :intuit_bank_account {:type :intuit_bank_account}
              :locations {:type '(list String)}}}
    :forecasted_transaction {:fields {:identifier {:type 'String}
                                      :id {:type :id}
                                      :day_of_month {:type 'Int}
                                      :amount {:type :money}}}
    :balance_sheet_account
    {:fields {:id {:type 'String}
              :amount {:type 'String}
              :location {:type 'String}
              :client_id {:type :id}
              :count {:type 'Int}
              :numeric_code {:type 'Int}
              :account_type {:type :account_type}
              :name {:type 'String}}}

    :balance_sheet
    {:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)}
              :comparable_balance_sheet_accounts {:type '(list :balance_sheet_account)}}}

    :profit_and_loss_report_period
    {:fields {:accounts {:type '(list :balance_sheet_account)}}}

    :profit_and_loss_report
    {:fields {:periods {:type '(list :profit_and_loss_report_period)}}}

    :address
    {:fields {:street1 {:type 'String}
              :street2 {:type 'String}
              :city {:type 'String}
              :state {:type 'String}
              :zip {:type 'String}}}

    :terms_override
    {:fields {:id {:type :id}
              :client {:type :client}
              :terms {:type 'Int}}}
              

    :schedule_payment_dom
    {:fields {:id {:type :id}
              :client {:type :client}
              :dom {:type 'Int}}}

    :vendor_account_override
    {:fields {:id {:type :id}
              :client {:type :client}
              :account {:type :account}}}

    :usage {:fields {:client_id {:type  :id}
                     :count {:type 'Int}}}

    :vendor
    {:fields {:id {:type :id}
              :name {:type 'String}
              :code {:type 'String}
              :terms {:type 'Int}
              :hidden {:type 'Boolean}
              :automatically_paid_when_due {:type '(list :client)}
              :terms_overrides {:type '(list :terms_override)}
              :schedule_payment_dom {:type '(list :schedule_payment_dom)}
              :account_overrides {:type '(list :vendor_account_override)}
              :usage {:type '(list :usage)}

              :print_as {:type 'String}
              :primary_contact {:type :contact}
              :secondary_contact {:type :contact}
              :address {:type :address}

              :default_account {:type :account}
              :invoice_reminder_schedule {:type 'String}

              :legal_entity_first_name {:type 'String}
              :legal_entity_middle_name {:type 'String}
              :legal_entity_last_name {:type 'String}
              :legal_entity_tin {:type 'String}
              :legal_entity_tin_type {:type :tin_type}
              :legal_entity_1099_type {:type :type_1099}}}

              
    :reminder
    {:fields {:id {:type 'Int}
              :email {:type 'String}
              :subject {:type 'String}
              :body {:type 'String}
              :scheduled {:type 'String}
              :sent {:type 'String}
              :vendor {:type :vendor}}}
              

    :journal_entry_line
    {:fields {:id {:type :id}
              :account {:type :account}
              :location {:type 'String}
              :debit {:type 'String}
              :credit {:type 'String}
              :running_balance {:type :money}}}
    :journal_entry
    {:fields {:id {:type :id}
              :source {:type 'String}
              :external_id {:type 'String}
              :original_entity {:type :id}
              :amount {:type 'String}
              :note {:type 'String}
              :cleared_against {:type 'String}
              :client {:type :client}
              :vendor {:type :vendor}
              :alternate_description {:type 'String}
              :date {:type 'String}
              :line_items {:type '(list :journal_entry_line)}}}
    

    :order_line_item
    {:fields {:id {:type :id}
              :item_name {:type 'String}
              :total {:type :money}
              :tax {:type :money}
              :category {:type 'String}
              :discount {:type :money}}}
    :charge
    {:fields {:id {:type :id}
              :processor {:type :processor}
              :type_name {:type 'String}
              :total {:type :money}
              :tip {:type :money}}}

    :sales_order
    {:fields {:id {:type :id}
              :location {:type 'String}
              :external_id {:type 'String}
              :total {:type :money}
              :tip {:type :money}
              :tax {:type :money}
              :discount {:type :money}
              :service_charge {:type :money}
              :returns {:type :money}
              :client {:type :client}
              :date {:type 'String}
              :charges {:type '(list :charge)}
              :line_items {:type '(list :order_line_item)}}}

    :expected_deposit
    {:fields {:id {:type :id}
              :location {:type 'String}
              :external_id {:type 'String}
              :total {:type :money}
              :fee {:type :money}
              :client {:type :client}
              :date {:type 'String}}}

    :check {:fields {:id {:type :id}
                     :type {:type 'String}
                     :amount {:type 'String}
                     :vendor {:type :vendor}
                     :date {:type 'String}
                     :bank_account {:type :bank_account}
                     :memo {:type 'String}
                     :s3_url {:type 'String}
                     :check_number {:type 'Int}
                     :status {:type 'String}
                     :invoices {:type '(list :invoice_payment)}}}
                     

    :payment {:fields {:id {:type :id}
                       :type {:type :payment_type}
                       :original_id {:type 'Int}
                       :amount {:type 'String}
                       :vendor {:type :vendor}
                       :client {:type :client}
                       :date {:type 'String}
                       :bank_account {:type :bank_account}
                       :memo {:type 'String}
                       :s3_url {:type 'String}
                       :check_number {:type 'Int}
                       :status {:type :ident}
                       :transaction {:type :transaction}
                       :invoices {:type '(list :invoice_payment)}}}
                     

    :yodlee_merchant {:fields {:id {:type :id}
                               :yodlee_id {:type 'String}
                               :name {:type 'String}}}

    :intuit_bank_account {:fields {:id {:type :id}
                                   :external_id {:type 'String}
                                   :name {:type 'String}}}

    :forecast_match {:fields {:id {:type :id}
                              :identifier {:type 'String}}}
    :transaction {:fields {:id {:type :id}
                           :amount {:type 'String}
                           :description_original {:type 'String}
                           :description_simple {:type 'String}
                           :location {:type 'String}
                           :forecast_match {:type :forecast_match}
                           :status {:type 'String}
                           :yodlee_merchant {:type :yodlee_merchant}
                           :client {:type :client}
                           :accounts {:type '(list :invoices_expense_accounts)}
                           :payment {:type :payment}
                           :expected_deposit {:type :expected_deposit}
                           :vendor {:type :vendor}
                           :bank_account {:type :bank_account}
                           :date {:type 'String}
                           :post_date {:type 'String}
                           :approval_status {:type :transaction_approval_status}
                           :matched_rule {:type :transaction_rule}}}

    :transaction_rule {:fields {:id {:type :id}
                                :note {:type 'String}
                                :client {:type :client}
                                :bank_account {:type :bank_account}
                                :yodlee_merchant {:type :yodlee_merchant}
                                :description {:type 'String}
                                :amount_lte {:type 'String}
                                :amount_gte {:type 'String}
                                :dom_lte {:type 'Int}
                                :dom_gte {:type 'Int}
                                :vendor {:type :vendor}
                                :accounts {:type '(list :percentage_account)}
                                :transaction_approval_status {:type :transaction_approval_status}}}

    :invoice_payment
    {:fields {:id {:type :id}
              :amount {:type 'String}
              :invoice_id {:type 'String}
              :payment_id {:type 'String}
              :payment {:type :payment}
              :invoice {:type :invoice}}}

    :user
    {:fields {:id {:type :id}
              :name {:type 'String}
              :role {:type 'String}
              :clients {:type '(list :client)}}}

    :account_client_override
    {:fields {:id {:type :id}
              :client {:type :client}
              :name {:type 'String}}}

    :account {:fields {:id {:type :id}
                       :numeric_code {:type 'Int}
                       :type {:type :ident}
                       :applicability {:type :applicability}
                       :account_set {:type 'String}
                       :location {:type 'String}
                       :name {:type 'String}
                       :client_overrides {:type '(list :account_client_override)}}}

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

    :percentage_account
    {:fields {:id {:type :id}
              :account {:type :account}
              :location  {:type 'String}
              :percentage {:type :percentage}}}
    :invoice
    {:fields {:id {:type :id}
              :original_id {:type 'Int}
              :client_identifier {:type 'String}
              :total {:type 'String}
              :outstanding_balance {:type 'String}
              :invoice_number {:type 'String}
              :status {:type 'String}
              :expense_accounts {:type '(list :invoices_expense_accounts)}
              :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}}}


    
    :yodlee_provider_account_page {:fields {:yodlee_provider_accounts {:type '(list :yodlee_provider_account)}
                                            :count {:type 'Int}
                                            :total {:type 'Int}
                                            :start {:type 'Int}
                                            :end {:type 'Int}}}

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

    :payment_page {:fields {:payments {:type '(list :payment)}
                            :count {:type 'Int}
                            :total {:type 'Int}
                            :start {:type 'Int}
                            :end {:type 'Int}}}

    :transaction_page {:fields {:data {:type '(list :transaction)}
                                :count {:type 'Int}
                                :total {:type 'Int}
                                :start {:type 'Int}
                                :end {:type 'Int}}}

    :import_batch {:fields {:user_name {:type 'String}
                            :id {:type :id}
                            :date {:type :iso_date}
                            :imported {:type 'Int}
                            :extant {:type 'Int}
                            :suppressed {:type 'Int}
                            :status {:type :import_batch_status}
                            :source {:type :import_batch_source}}}

    :import_batch_page {:fields {:data {:type '(list :import_batch)}
                                 :count {:type 'Int}
                                 :total {:type 'Int}
                                 :start {:type 'Int}
                                 :end {:type 'Int}}}

    :transaction_rule_page {:fields {:transaction_rules {:type '(list :transaction_rule)}
                                     :count {:type 'Int}
                                     :total {:type 'Int}
                                     :start {:type 'Int}
                                     :end {:type 'Int}}}

    :ledger_page {:fields {:journal_entries {:type '(list :journal_entry)}
                           :count {:type 'Int}
                           :total {:type 'Int}
                           :start {:type 'Int}
                           :end {:type 'Int}}}
 
    :sales_order_page {:fields {:sales_orders {:type '(list :sales_order)}
                                :count {:type 'Int}
                                :total {:type 'Int}
                                :start {:type 'Int}
                                :end {:type 'Int}
                                :sales_order_total {:type :money}
                                :sales_order_tax {:type :money}}}
                              
                       

    :expected_deposit_page {:fields {:expected_deposits {:type '(list :expected_deposit)}
                                     :count {:type 'Int}
                                     :total {:type 'Int}
                                     :start {:type 'Int}
                                     :end {:type 'Int}}}

    :reminder_page {:fields {:reminders {:type '(list :reminder)}
                             :count {:type 'Int}
                             :total {:type 'Int}
                             :start {:type 'Int}
                             :end {:type 'Int}}}
    :check_result {:fields {:invoices {:type '(list :invoice)}
                            :pdf_url {:type 'String}}}

    :expense_account_stat {:fields {:account {:type :account}
                                    :total {:type 'String}}}

    :invoice_stat {:fields {:name {:type 'String}
                            :paid {:type 'String}
                            :unpaid {:type 'String}}}

    :import_ledger_entry_result {:fields {:external_id {:type 'String}
                                          :error {:type 'String}
                                          :status {:type 'String}}}

    :import_ledger_result {:fields {:successful {:type '(list :import_ledger_entry_result)}
                                    :existing {:type '(list :import_ledger_entry_result)}
                                    :ignored {:type '(list :import_ledger_entry_result)}
                                    :errors {:type '(list :import_ledger_entry_result)}}}
                                    

    :upcoming_transaction {:fields {:amount {:type :money}
                                    :identifier {:type 'String}
                                    :date {:type :iso_date}}}

    :cash_flow_result {:fields {:beginning_balance {:type :money}
                                :invoices_due_soon {:type '(list :invoice)}
                                :outstanding_payments {:type :money}
                                :upcoming_credits {:type '(list :upcoming_transaction)}
                                :upcoming_debits {:type '(list :upcoming_transaction)}}}}
    



   :queries
   {:expense_account_stats {:type '(list :expense_account_stat)
                            :args {:client_id {:type :id}}
                            :resolve :get-expense-account-stats}

    :test_transaction_rule {:type '(list :transaction)
                              :args {:transaction_rule {:type :edit_transaction_rule}}
                              :resolve :test-transaction-rule}

    :run_transaction_rule {:type '(list :transaction)
                            :args {:transaction_rule_id {:type :id}}
                           :resolve :run-transaction-rule}

    :invoice_stats {:type '(list :invoice_stat)
                    :args {:client_id {:type :id}}
                    :resolve :get-invoice-stats}

    :cash_flow {:type :cash_flow_result
                :args {:client_id {:type :id}}
                :resolve :get-cash-flow}

    :potential_payment_matches {:type '(list :payment)
                                :args {:transaction_id {:type :id}}
                                :resolve :get-potential-payments}

    :potential_autopay_invoices_matches {:type '(list (list :invoice))
                                         :args {:transaction_id {:type :id}}
                                         :resolve :get-potential-autopay-invoices-matches}
    :potential_unpaid_invoices_matches {:type '(list (list :invoice))
                                         :args {:transaction_id {:type :id}}
                                         :resolve :get-potential-unpaid-invoices-matches}

    :potential_transaction_rule_matches {:type '(list :transaction_rule)
                                         :args {:transaction_id {:type :id}}
                                         :resolve :get-transaction-rule-matches}
    :balance_sheet {:type :balance_sheet
                    :args {:client_id {:type :id}
                           :include_comparison {:type 'Boolean}
                           :date {:type :iso_date}
                           :comparison_date {:type :iso_date}}
                    :resolve :get-balance-sheet}

    :profit_and_loss {:type :profit_and_loss_report
                      :args {:client_id {:type :id}
                             :client_ids {:type '(list :id)}
                             :periods {:type '(list :date_range)}}
                      :resolve :get-profit-and-loss}

    
    :yodlee_provider_account_page {:type :yodlee_provider_account_page
                                   :args {:client_id {:type :id}}
                                   :resolve :get-yodlee-provider-account-page}

    :invoice_page {:type '(list :invoice_page)
                   :args {: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}
                          :client_id {:type :id}
                          :vendor_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)}}
                   
                   :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}

    :accounts {:type '(list :account)
               :args {:account_set {:type 'String}}
               :resolve :get-accounts}

    :all_payments {:type '(list :payment)
                   :args {:client_id {:type :id}
                          :client_code {:type 'String}
                          :original_id {:type 'Int}
                          :statuses {:type '(list String)}}
                   :resolve :get-all-payments}

    :all_expected_deposits {:type '(list :expected_deposit)
                            :args {:client_id {:type :id}
                                   :client_code {:type 'String}}
                            :resolve :get-all-expected-deposits}

    :all_sales_orders {:type '(list :sales_order)
                       :args {:client_id {:type :id}
                              :date_range {:type :date_range}
                              :client_code {:type 'String}}
                       :resolve :get-all-sales-orders}

    :yodlee_merchants {:type '(list :yodlee_merchant)
                       :args {}
                       :resolve :get-yodlee-merchants}
    
    :intuit_bank_accounts {:type '(list :intuit_bank_account)
                           :args {}
                           :resolve :get-intuit-bank-accounts}

    :transaction_page {:type :transaction_page
                       :args {:filters {:type :transaction_filters}}
                   
                       :resolve :get-transaction-page}

    :import_batch_page {:type :import_batch_page
                        :args {:filters {:type :import_batch_filters}}
                        
                        :resolve :get-import-batch-page}

    :transaction_rule_page {:type :transaction_rule_page
                            :args {:client_id {:type :id}
                                   :vendor_id {:type :id}
                                   :start {:type 'Int}
                                   :per_page {:type 'Int}
                                   :sort {:type '(list :sort_item)}
                                   :asc {:type 'Boolean}
                                   :note {:type 'String}
                                   :description {:type 'String}}
                            :resolve :get-transaction-rule-page}

    :ledger_page {:type :ledger_page
                  :args {:filters {:type :ledger_filters}}
                  :resolve :get-ledger-page}

    :sales_order_page {:type :sales_order_page
                       :args {:client_id {:type :id}
                              :date_range {:type :date_range}
                              :total_lte {:type :money}
                              :total_gte {:type :money}
                              :processor {:type :processor}
                              :start {:type 'Int}
                              :per_page {:type 'Int}
                              :sort {:type '(list :sort_item)}}
                       
                       :resolve :get-sales-order-page}

    :expected_deposit_page {:type :expected_deposit_page
                            :args {:client_id {:type :id}
                                   :exact_match_id {:type :id}
                                   :date_range {:type :date_range}
                                   :total_lte {:type :money}
                                   :total_gte {:type :money}
                                   :start {:type 'Int}
                                   :per_page {:type 'Int}
                                   :sort {:type '(list :sort_item)}}
                            
                            :resolve :get-expected-deposit-page}

    :payment_page {:type '(list :payment_page)
                   :args {:client_id {:type :id}
                          :vendor_id {:type :id}
                          :payment_type {:type :payment_type}
                          :exact_match_id {:type :id}
                          :date_range {:type :date_range}
                          :amount_lte {:type :money}
                          :amount_gte {:type :money}
                          :check_number_like {:type 'String}
                          :invoice_number {:type 'String}
                          :start {:type 'Int}
                          :per_page {:type 'Int}
                          :sort {:type '(list :sort_item)}}
                   
                   :resolve :get-payment-page}
    
    :client {:type '(list :client)
              :resolve :get-client}
    :vendor {:type '(list :vendor)
             :resolve :get-vendor}
    :user {:type '(list :user)
           :resolve :get-user}}

    

   :input-objects
   {
    :sort_item
    {:fields {:sort_key {:type 'String}
              :sort_name {:type 'String}
              :asc {:type 'Boolean}}}

    :transaction_filters {:fields {:client_id {:type :id}
                                   :exact_match_id {:type :id}
                                   :import_batch_id {:type :id}

                                   :vendor_id {:type :id}
                                   :bank_account_id {:type :id}
                                   :account_id {:type :id}
                                   :date_range {:type :date_range}
                                   :location {:type 'String}
                                   :amount_lte {:type :money}
                                   :amount_gte {:type :money}
                                   :description {:type 'String}
                                   :start {:type 'Int}
                                   :per_page {:type 'Int}
                                   :sort {:type '(list :sort_item)}
                                   :approval_status {:type :transaction_approval_status}
                                   :unresolved {:type 'Boolean}}}
    :import_batch_filters {:fields {:start {:type 'Int}
                                    :per_page {:type 'Int}
                                    :sort {:type '(list :sort_item)}}}

    :ledger_filters {:fields {:client_id {:type :id}
                              :vendor_id {:type :id}
                              :account_id {:type :id}
                              :amount_lte {:type :money}
                              :amount_gte {:type :money}
                              :bank_account_id {:type :id}
                              :date_range {:type :date_range}
                              :location {:type 'String}
                              :from_numeric_code {:type 'Int}
                              :to_numeric_code {:type 'Int}
                              :start {:type 'Int}
                              :per_page {:type 'Int}
                              :only_external {:type 'Boolean}
                              :external_id_like {:type 'String}
                              :sort {:type '(list :sort_item)}}}
    :invoice_payment_amount {:fields {:invoice_id {:type :id}
                                      :amount {:type :money}}}
    :edit_location_match {:fields {:location {:type 'String}
                                   :match {:type 'String}
                                   :id {:type :id}}}

    :edit_forecasted_transaction {:fields {:identifier {:type 'String}
                                           :id {:type :id}
                                           :day_of_month {:type 'Int}
                                           :amount {:type :money}}}


    :date_range {:fields {:start {:type :iso_date}
                          :end {:type :iso_date}}}

    :import_ledger_line_item {:fields {:account_identifier {:type 'String}
                                       :location {:type 'String}
                                       :debit {:type :money}
                                       :credit {:type :money}}}
    :import_ledger_entry {:fields {:source {:type 'String}
                                   :external_id {:type 'String} 
                                   :client_code {:type 'String}
                                   :date {:type 'String}
                                   :vendor_name {:type 'String}
                                   :amount {:type :money}
                                   :note {:type 'String}
                                   :cleared_against {:type 'String}
                                   :line_items {:type '(list :import_ledger_line_item)}}}

    :edit_client {:fields {:id {:type :id}
                           :name {:type 'String}
                           :code {:type 'String}
                           :signature_data {:type 'String}
                           :email {:type 'String}
                           :week_a_credits {:type :money}
                           :week_a_debits {:type :money}
                           :week_b_credits {:type :money}
                           :week_b_debits {:type :money}
                           :address {:type :add_address}
                           :locations {:type '(list String)}
                           :matches {:type '(list String)}
                           :location_matches {:type '(list :edit_location_match)}
                           :bank_accounts {:type '(list :edit_bank_account)}
                           :forecasted_transactions {:type '(list :edit_forecasted_transaction)}}}
                           
    :edit_bank_account
    {:fields {:id {:type :id}
              :code {:type 'String}
              :type {:type :bank_account_type}
              :start_date {:type :iso_date}
              :number {:type 'String}
              :check_number {:type 'Int}
              :numeric_code {:type 'Int}
              :visible {:type 'Boolean}
              :include_in_reports {:type 'Boolean}
              :sort_order {:type 'Int}
              :name {:type 'String}
              :bank_code {:type 'String}
              :routing {:type 'String}
              :bank_name {:type 'String}
              :locations {:type '(list String)}
              :yodlee_account_id {:type 'Int}
              :intuit_bank_account {:type :id}
              :yodlee_account {:type 'Int}}}
    :edit_user
    {:fields {:id {:type :id}
              :name {:type 'String}
              :role {:type 'String}
              :clients {:type '(list String)}}}

    :add_contact
    {:fields {:id {:type :id}
              :name {:type 'String}
              :email {:type 'String}
              :phone {:type 'String}}}
    :add_address
    {:fields {:street1 {:type 'String}
              :street2 {:type 'String}
              :city {:type 'String}
              :state {:type 'String}
              :zip {:type 'String}}}

    :add_terms_override
    {:fields {:id {:type :id}
              :client_id {:type :id}
              :terms {:type 'Int}}}
              

    :add_account_override
    {:fields {:id {:type :id}
              :client_id {:type :id}
              :account_id {:type :id}}}

    :add_schedule_payment_dom
    {:fields {:id {:type :id}
              :client_id {:type :id}
              :dom {:type 'Int}}}

    :add_vendor
    {:fields {:id {:type :id}
              :name {:type 'String}
              :terms {:type 'Int}
              :terms_overrides {:type '(list :add_terms_override)}
              :code {:type 'String}
              :automatically_paid_when_due {:type '(list :id)}

              :hidden {:type 'Boolean}
              :print_as {:type 'String}
              :primary_contact {:type :add_contact}
              :secondary_contact {:type :add_contact}
              :address {:type :add_address}

              :default_account_id {:type :id}
              :account_overrides {:type '(list :add_account_override)}
              :schedule_payment_dom {:type '(list :add_schedule_payment_dom)}
              :invoice_reminder_schedule {:type 'String}

              :legal_entity_first_name {:type 'String}
              :legal_entity_middle_name {:type 'String}
              :legal_entity_last_name {:type 'String}
              :legal_entity_tin {:type 'String}
              :legal_entity_tin_type {:type :tin_type}
              :legal_entity_1099_type {:type :type_1099}}}
              

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

    :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_transaction
    {:fields {:id {:type :id}
              :vendor_id {:type :id}
              :forecast_match {:type :id}
              :approval_status {:type :transaction_approval_status}
              :accounts {:type '(list :edit_expense_account)}}}

    :edit_percentage_account
    {:fields {:id {:type :id}
              :account_id {:type :id}
              :location  {:type 'String}
              :percentage {:type :percentage}}}

    :edit_transaction_rule
    {:fields {:id {:type :id}
              :description {:type 'String}
              :note {:type 'String}
              :bank_account_id {:type :id}
              :client_id {:type :id}
              :yodlee_merchant_id {:type :id}
              :amount_lte {:type :money}
              :amount_gte {:type :money}
              :dom_lte {:type 'Int}
              :dom_gte {:type 'Int}
              :vendor_id {:type :id}
              :accounts {:type '(list :edit_percentage_account)}
              :transaction_approval_status {:type :transaction_approval_status}}}

    :edit_account_client_override
    {:fields {:id {:type :id}
              :client_id {:type :id}
              :name {:type 'String}}}

    :edit_account
    {:fields {:id {:type :id}
              :type {:type :account_type}
              :applicability {:type :applicability}
              :numeric_code {:type 'Int}
              :location {:type 'String}
              :account_set {:type 'String}
              :name {:type 'String}
              :client_overrides {:type '(list :edit_account_client_override)}}}}

   :enums {:payment_type {:values [{:enum-value :check}
                                   {:enum-value :cash}
                                   {:enum-value :debit}]}

           :import_batch_source {:values [{:enum-value :intuit}
                                          {:enum-value :yodlee}
                                          {:enum-value :yodlee2}
                                          {:enum-value :manual}]}
           :import_batch_status {:values [{:enum-value :started}
                                          {:enum-value :completed}]}

           :processor {:values [{:enum-value :na}
                                {:enum-value :doordash}
                                {:enum-value :uber_eats}
                                {:enum-value :grubhub}]}
           :tin_type {:values [{:enum-value :ein}
                               {:enum-value :ssn}]}
           :type_1099 {:values [{:enum-value :none}
                                {:enum-value :misc}
                                {:enum-value :landlord}]}
           :invoice_status {:values [{:enum-value :paid}
                                     {:enum-value :unpaid}
                                     {:enum-value :voided}]}
           :bank_account_type {:values [{:enum-value :check}
                                        {:enum-value :credit}
                                        {:enum-value :cash}]}
           :applicability {:values [{:enum-value :global}
                                    {:enum-value :optional}
                                    {:enum-value :customized}]}
           :account_type {:values [{:enum-value :dividend}
                                   {:enum-value :expense}
                                   {:enum-value :asset}
                                   {:enum-value :liability}
                                   {:enum-value :equity}
                                   {:enum-value :revenue}]}
           :transaction_approval_status {:values [{:enum-value :approved}
                                                  {:enum-value :unapproved}
                                                  {:enum-value :requires_feedback}
                                                  {:enum-value :excluded}]}}
   :mutations
   {: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}

    :bulk_change_transaction_status {:type :message
                                     :args {:filters {:type :transaction_filters}
                                            :status {:type :transaction_approval_status}
                                            :ids {:type '(list :id)}}
                                     :resolve :mutation/bulk-change-transaction-status}

    :delete_external_ledger {:type :message
                             :args {:filters {:type :ledger_filters}
                                    :ids {:type '(list :id)}}
                             :resolve :mutation/delete-external-ledger}
    :delete_transactions {:type :message
                          :args {:filters {:type :transaction_filters}
                                 :ids {:type '(list :id)}}
                          :resolve :mutation/delete-transactions}
    :delete_transaction_rule {:type :id
                              :args {:transaction_rule_id  {:type :id}}
                              :resolve :mutation/delete-transaction-rule}
    :merge_vendors {:type :id
                    :args {:from  {:type :id}
                           :to  {:type :id}}
                    :resolve :mutation/merge-vendors}

    :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}

    :print_checks {:type :check_result
                   :args {:invoice_payments  {:type '(list :invoice_payment_amount)}
                          :bank_account_id {:type :id}
                          :type {:type :payment_type}
                          :client_id {:type :id}}
                   :resolve :mutation/print-checks}

    :add_handwritten_check {:type :check_result
                            :args {:invoice_payments  {:type '(list :invoice_payment_amount)}
                                   :date {:type 'String}
                                   :check_number {:type 'Int}
                                   :bank_account_id {:type :id}}
                            :resolve :mutation/add-handwritten-check}
    :edit_user {:type :user
                :args {:edit_user {:type :edit_user}}
                :resolve :mutation/edit-user}

    :edit_client {:type :client
                  :args {:edit_client {:type :edit_client}}
                  :resolve :mutation/edit-client}

    :upsert_vendor {:type :vendor
                    :args {:vendor {:type :add_vendor}}
                    :resolve :mutation/upsert-vendor}

    :upsert_transaction_rule {:type :transaction_rule
                              :args {:transaction_rule {:type :edit_transaction_rule}}
                              :resolve :mutation/upsert-transaction-rule}
    :add_invoice {:type :invoice
                  :args {:invoice {:type :add_invoice}}
                  :resolve :mutation/add-invoice}

    :import_ledger {:type :import_ledger_result
                    :args {:entries {:type '(list :import_ledger_entry)}}
                    :resolve :mutation/import-ledger}
    :edit_invoice {:type :invoice
                   :args {:invoice {:type :edit_invoice}}
                   :resolve :mutation/edit-invoice}
    :upsert_account {:type :account
                     :args {:account {:type :edit_account}}
                     :resolve :mutation/upsert-account}
    :edit_transaction {:type :transaction
                       :args {:transaction {:type :edit_transaction}}
                       :resolve :mutation/edit-transaction}

    :match_transaction {:type :transaction
                        :args {:transaction_id {:type :id}
                               :payment_id {:type :id}}
                        :resolve :mutation/match-transaction}

    :match_transaction_autopay_invoices {:type :transaction
                                         :args {:transaction_id {:type :id}
                                                :autopay_invoice_ids {:type '(list :id)}}
                                         :resolve :mutation/match-transaction-autopay-invoices}

    :match_transaction_unpaid_invoices {:type :transaction
                                        :args {:transaction_id {:type :id}
                                               :unpaid_invoice_ids {:type '(list :id)}}
                                        :resolve :mutation/match-transaction-unpaid-invoices}

    :match_transaction_rules {:type '(list :transaction)
                              :args {:transaction_ids {:type '(list :id)}
                                     :all {:type 'Boolean}
                                     :transaction_rule_id {:type :id}}
                              :resolve :mutation/match-transaction-rules}

    :unlink_transaction {:type :transaction
                         :args {:transaction_id {:type :id}}
                         :resolve :mutation/unlink-transaction}
    :void_invoice {:type :invoice
                   :args {:invoice_id {:type :id}}
                   :resolve :mutation/void-invoice}
    :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}

    :void_payment {:type :payment
                   :args {:payment_id {:type :id}}
                   :resolve :mutation/void-payment}
    :edit_expense_accounts {:type :invoice
                            :args {:invoice_id {:type :id}
                                   :expense_accounts {:type '(list :edit_expense_account)}}
                            :resolve :mutation/edit-expense-accounts}}})


(defn snake->kebab [s]
  (str/replace s #"_" "-"))

(defn kebab [x]
  (keyword (snake->kebab (name x))))

(defn kebab->snake [s]
  (str/replace s #"-" "_"))

(defn snake [x]
  (keyword (kebab->snake (name x))))

(defn ->graphql [m]
  (walk/postwalk
    (fn [node]
      (cond

        (keyword? node)
        (snake node)

        :else
        node))
    m))

(defn <-graphql [m]
  (walk/postwalk
    (fn [node]
      (cond

        (keyword? node)
        (kebab node)

        :else
        node))
    m))


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

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

(defn get-user [context args value]
  (assert-admin (:id context))

  (let [users (d-users/get-graphql args)]
    (->graphql users)))



(defn print-checks [context args value]
  
  (assert-can-see-client (:id context) (:client_id args))
  (->graphql
   (gq-checks/print-checks (map (fn [i] {:invoice-id (:invoice_id i)
                                         :amount (:amount i)})
                                (:invoice_payments args))
                           (:client_id args)
                           (:bank_account_id args)
                           (:type args)
                           (:id context))))

(defn get-expense-account-stats [context {:keys [client_id] } value]
  (let [result (cond-> {:query {:find ['?account '?account-name '(sum ?amount)]
                                :in ['$]
                                :where []}
                        :args [(d/db (d/connect uri)) client_id]}
                 client_id (merge-query {:query {:in ['?c]}
                                         
                                         :args [client_id]})
                 (not client_id) (merge-query {:query {:where ['[?c :client/name]]}})

                 true (merge-query {:query {:where ['[?i :invoice/client ?c]
                                                    '[?i :invoice/expense-accounts ?expense-account]
                                                    '[?expense-account :invoice-expense-account/account ?account]
                                                    '[?account :account/name ?account-name]
                                                    '[?expense-account :invoice-expense-account/amount ?amount]]}})

                 true (d/query))]
    (for [[account-id account-name total] result]
      {:account {:id account-id :name account-name} :total total})))

(defn categorize [x]
  (cond (<= x 0) :due
        (<= x 30 ) :due-30
        (<= x 60 ) :due-60
        :else :due-later))

(defn get-invoice-stats [context {:keys [client_id] } value]
  (let [result (cond-> {:query {:find ['?name '(sum ?outstanding-balance) '(sum ?total)]
                                :in ['$]
                                :where []}
                        :args [(d/db (d/connect uri)) client_id]}
                 client_id (merge-query {:query {:in ['?c]}
                                         :args [client_id]})
                 (not client_id) (merge-query {:query {:where ['[?c :client/name]]}})

                 true (merge-query {:query {:where ['[?i :invoice/client ?c]
                                                    '[?i :invoice/outstanding-balance ?outstanding-balance]
                                                    '[?i :invoice/total ?total]
                                                    '[?i :invoice/due ?date]
                                                    '[(.toInstant ^java.util.Date ?date) ?d2]
                                                    '[(.between java.time.temporal.ChronoUnit/DAYS (java.time.Instant/now) ?d2 ) ?d3]
                                                    '[(auto-ap.graphql/categorize ?d3) ?name]]}})

                 true (d/query))
        result (group-by first result)]
    
    (for [[id name] [[:due "Due"] [:due-30 "0-30 days"] [:due-60 "31-60 days"] [:due-later ">60 days"]]
          :let [[[_ outstanding-balance total] ] (id result nil)
                outstanding-balance (or outstanding-balance 0)
                total (or total 0)]]
      {:name name :unpaid outstanding-balance :paid (if (= :due id)
                                                      0
                                                      (- total outstanding-balance))})))

(defn has-fulfilled? [id date recent-fulfillments]
  
  (seq (transduce
        (filter (fn [[potential-id potential-date]]
                  (let [date (coerce/to-date-time date)
                        potential-date (coerce/to-date-time potential-date)]
                    (and (= id potential-id)
                         (<= (t/in-days (apply t/interval (sort [date potential-date]))) 10)))))
        conj
        []
        recent-fulfillments)))

(def first-week-a (coerce/to-date-time #inst "1999-12-27T00:00:00.000-07:00"))

(defn get-cash-flow [context {:keys [client_id]} value]
  (when client_id
    (let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions ]} (d/pull (d/db (d/connect uri)) '[*] client_id)
          total-cash (reduce
                      (fn [total [credit debit]]
                        (- (+ total credit)
                           debit))
                      0.0
                      (d/query {:query {:find '[?debit ?credit]
                                        :in '[$ ?client]
                                        :where ['[?j :journal-entry/client ?client]
                                                '[?j :journal-entry/line-items ?je]
                                                '[?je :journal-entry-line/account ?ba]
                                                '[?ba :bank-account/type :bank-account-type/check]
                                                '[(get-else $ ?je :journal-entry-line/debit 0.0) ?debit]
                                                '[(get-else $ ?je :journal-entry-line/credit 0.0) ?credit]]}
                                :args [(d/db (d/connect uri))  client_id]}))
          bills-due-soon (d/query {:query {:find '[?due ?outstanding ?invoice-number ?vendor-id]
                                           :in '[$ ?client ?due-before]
                                           :where ['[?i :invoice/client ?client]
                                                   '[?i :invoice/status :invoice-status/unpaid]
                                                   '[?i :invoice/due ?due]
                                                   '[(<= ?due ?due-before)]
                                                   '[?i :invoice/outstanding-balance ?outstanding]
                                                   '[?i :invoice/invoice-number ?invoice-number]
                                                   '[?i :invoice/vendor ?vendor-id]]}
                                   :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]})
          outstanding-checks (reduce
                              +
                              0.0
                              (map first (d/query {:query {:find '[?amount]
                                                           :in '[$ ?client ?due-before]
                                                           :where ['[?p :payment/client ?client]
                                                                   '[?p :payment/status  :payment-status/pending]
                                                                   '[?p :payment/amount ?amount]
                                                                   '(or 
                                                                     [?p :payment/type :payment-type/debit]
                                                                     [?p :payment/type :payment-type/check])]}
                                                   :args [(d/db (d/connect uri))  client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]})))
          recent-fulfillments (d/query {:query {:find '[?f ?d]
                                                :in '[$ ?client ?min-date]
                                                :where ['[?t :transaction/forecast-match ?f]
                                                        '[?t :transaction/date ?d]
                                                        '[?t :transaction/client ?client]
                                                        '[(>= ?d ?min-date)]]}
                                        :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/months -2)))]})
          forecasted-transactions (for [{:forecasted-transaction/keys [amount identifier day-of-month]
                                         :db/keys [id]} forecasted-transactions
                                        month (range -1 7)
                                        :let [next (t/plus (t/local-date (t/year (time/local-now))
                                                                         (t/month (time/local-now))
                                                                         (Math/min (Math/max day-of-month 1)
                                                                                   30))
                                                           (t/months month))]
                                        :when (not (has-fulfilled? id next recent-fulfillments))]
                                    {:identifier identifier
                                     :amount amount
                                     :date (coerce/to-date-time next)})
          is-week-a? (fn [d]
                       (= 0 (mod (t/in-weeks (t/interval first-week-a d)) 2)))]
      
      {:beginning_balance total-cash
       :outstanding_payments outstanding-checks
       :invoices_due_soon (mapv (fn [[due outstanding invoice-number vendor-id]]
                                  {:due (coerce/to-date-time due)
                                   :invoice_number invoice-number
                                   :vendor {:id vendor-id}
                                   :outstanding_balance outstanding})
                                bills-due-soon)
       :upcoming_credits (into (mapv
                                (fn [date]
                                  {:amount (if (is-week-a? (coerce/to-date-time date))
                                             (or week-a-credits 0)
                                             (or week-b-credits 0))
                                   :date (coerce/to-date-time date)})
                                (take (* 7 4) (time/day-of-week-seq 1)))
                               (filter #(>= (:amount %) 0) forecasted-transactions))
       :upcoming_debits (into (mapv
                               (fn [date]
                                 {:amount (- (if (is-week-a? (coerce/to-date-time date))
                                               (or week-a-debits 0)
                                               (or week-b-debits 0)))
                                  :date (coerce/to-date-time date)})
                               (take (* 7 4) (time/day-of-week-seq 1)))
                              (filter #(< (:amount %) 0) forecasted-transactions))})))
       

(def schema
  (-> integreat-schema
      (attach-resolvers {:get-invoice-page gq-invoices/get-invoice-page
                         :get-all-invoices gq-invoices/get-all-invoices
                         :get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page
                         :get-all-payments get-all-payments
                         :get-all-expected-deposits gq-expected-deposit/get-all-expected-deposits
                         :get-expected-deposit-page gq-expected-deposit/get-expected-deposit-page
                         :get-all-sales-orders get-all-sales-orders
                         :get-payment-page gq-checks/get-payment-page
                         :get-potential-payments gq-checks/get-potential-payments
                         :get-potential-autopay-invoices-matches gq-transactions/get-potential-autopay-invoices-matches
                         :get-potential-unpaid-invoices-matches gq-transactions/get-potential-unpaid-invoices-matches
                         :get-accounts gq-accounts/get-accounts
                         :get-transaction-page gq-transactions/get-transaction-page
                         :get-import-batch-page gq-import-batches/get-import-batch-page
                         :get-ledger-page gq-ledger/get-ledger-page
                         :get-sales-order-page gq-sales-orders/get-sales-orders-page
                         :get-balance-sheet gq-ledger/get-balance-sheet
                         :get-profit-and-loss gq-ledger/get-profit-and-loss
                         :get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page
                         :get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches
                         :get-expense-account-stats get-expense-account-stats
                         :get-invoice-stats get-invoice-stats
                         :get-cash-flow get-cash-flow
                         :get-yodlee-merchants ym/get-yodlee-merchants
                         :get-intuit-bank-accounts gq-intuit-bank-accounts/get-intuit-bank-accounts
                         :get-client gq-clients/get-client
                         :get-user get-user
                         :mutation/add-handwritten-check gq-checks/add-handwritten-check
                         :mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
                         :mutation/print-checks print-checks
                         :mutation/reject-invoices gq-invoices/reject-invoices
                         :mutation/approve-invoices gq-invoices/approve-invoices
                         :mutation/edit-user gq-users/edit-user
                         :mutation/add-invoice gq-invoices/add-invoice
                         :mutation/add-and-print-invoice gq-invoices/add-and-print-invoice
                         :mutation/edit-invoice gq-invoices/edit-invoice
                         :mutation/edit-transaction gq-transactions/edit-transaction
                         :mutation/unlink-transaction gq-transactions/unlink-transaction
                         :mutation/bulk-change-transaction-status gq-transactions/bulk-change-status
                         :mutation/delete-external-ledger gq-ledger/delete-external-ledger
                         :mutation/delete-transactions gq-transactions/delete-transactions
                         :mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule
                         :test-transaction-rule gq-transaction-rules/test-transaction-rule
                         :run-transaction-rule gq-transaction-rules/run-transaction-rule
                         :mutation/match-transaction gq-transactions/match-transaction
                         :mutation/match-transaction-autopay-invoices gq-transactions/match-transaction-autopay-invoices
                         :mutation/match-transaction-unpaid-invoices gq-transactions/match-transaction-unpaid-invoices
                         :mutation/match-transaction-rules gq-transactions/match-transaction-rules
                         :mutation/edit-client gq-clients/edit-client
                         :mutation/upsert-vendor gq-vendors/upsert-vendor
                         :mutation/upsert-account gq-accounts/upsert-account
                         :mutation/merge-vendors gq-vendors/merge-vendors
                         :mutation/void-invoice gq-invoices/void-invoice
                         :mutation/unvoid-invoice gq-invoices/unvoid-invoice
                         :mutation/unautopay-invoice gq-invoices/unautopay-invoice
                         :mutation/void-payment gq-checks/void-check
                         :mutation/edit-expense-accounts gq-invoices/edit-expense-accounts
                         :mutation/import-ledger gq-ledger/import-ledger
                         :get-vendor gq-vendors/get-graphql})
      schema/compile))



(defn simplify
  "Converts all ordered maps nested within the map into standard hash maps, and
   sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems."
  [m]
  (walk/postwalk
    (fn [node]
      (cond
        (instance? IPersistentMap node)
        (into {} node)

        (seq? node)
        (vec node)

        (keyword? node)
        (kebab node)

        :else
        node))
    m))

(defn query
  ([id q]
   (query id q nil))
  ([id q v]
   (lc/with-context {:query q}
     (log/info "Executing query" q)
     (try 
       (let [[result time] (time-it (simplify (execute schema q v {:id id})))]
         (info-event "Query completed"
                     {:time (:time time)
                      :errors (seq (:errors result))})
         (when (seq (:errors result))
           (throw (ex-info "GraphQL error" {:result result})))
         result)

       (catch Exception e
         (if-let [v (or (:validation-error (ex-data e))
                        (:validation-error (ex-data (.getCause e))))]
                        
           (do 
             (warn-event "validation error" {:validation-error v
                                             :data (ex-data e)})
             (throw e)
             #_{:errors [{:message v}]})
           (do 
             (error-event "query error" {:error e})

             (throw e))))))))
