(ns auto-ap.graphql
  (:require
   [auto-ap.datomic :refer [conn merge-query query2]]
   [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.ezcater :as gq-ezcater]
   [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.plaid :as gq-plaid]
   [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.utils :refer [attach-tracing-resolvers]]
   [auto-ap.graphql.vendors :as gq-vendors]
   [auto-ap.graphql.yodlee-merchants :as ym]
   [auto-ap.logging :as alog]
   [auto-ap.time :as time]
   [clj-time.coerce :as coerce]
   [clj-time.core :as t]
   [clojure.string :as str]
   [clojure.walk :as walk]
   [com.brunobonacci.mulog :as mu]
   [com.unbounce.dogstatsd.core :as statsd]
   [com.walmartlabs.lacinia :refer [execute]]
   [com.walmartlabs.lacinia.parser :as p]
   [com.walmartlabs.lacinia.schema :as schema]
   [datomic.api :as dc]
   [yang.time :refer [time-it]])
  (:import
   (clojure.lang IPersistentMap)))

(def integreat-schema
  {
   :scalars {:id {:parse #(cond (number? %)
                                %

                                %
                                (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)}
             :iso_date_time {:parse #(clj-time.coerce/to-date-time %)
                             :serialize #(str (clj-time.coerce/to-date-time %))}
             :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}}}

    :search_result
    {:fields {:name {:type 'String}
              :id {:type :id}}}

    :account_search_result
    {:fields {:name {:type 'String}
              :id   {:type :id}
              :location {:type 'String}
              :warning {:type 'String}}}

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

    :integration_status
    {:fields {:id {:type :id}
              :last_attempt {:type :iso_date}
              :last_updated {:type :iso_date}
              :message {:type 'String}
              :state {:type :integration_state}}}

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

    
        :address
    {:fields {:id {:type :id}
              :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}
              :plaid_merchant {:type :plaid_merchant}
              :secondary_contact {:type :contact}
              :address {:type :address}

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

              :legal_entity_name {: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}}}
    
    :yodlee_merchant {:fields {:id {:type :id}
                               :yodlee_id {:type 'String}
                               :name {:type 'String}}}

    :plaid_merchant {:fields {:id {:type :id}
                             :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_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}}}

    

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

    :csv
    {:fields {:csv_content_b64 {:type 'String}}}

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

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

    :percentage_account
    {:fields {:id {:type :id}
              :account {:type :account}
              :location  {:type 'String}
              :percentage {:type :percentage}}}

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

    

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

    

    

    :vendor_page {:fields {:vendors      {:type '(list :vendor)}
                           :count             {:type 'Int}
                           :total             {:type 'Int}
                           :start             {:type 'Int}
                           :end               {:type 'Int}}}

    :account_page {:fields {:accounts {:type '(list :account)}
                             :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}}}

        :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}
    :potential_transaction_rule_matches {:type '(list :transaction_rule)
                                         :args {:transaction_id {:type :id}}
                                         :resolve :get-transaction-rule-matches}
    

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

    :all_accounts {:type    '(list :account)
                   :args    {}
                   :resolve :get-all-accounts}

    :search_vendor {:type '(list :search_result)
                    :args {:query {:type 'String}}
                    :resolve :search-vendor}
    :search_account {:type    '(list :account_search_result)
                     :args    {:query {:type 'String}
                               :allowance  {:type :account_allowance}
                               :client_id {:type :id}
                               :vendor_id {:type :id}}
                    :resolve :search-account}

    

    

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

    

    
    :vendor {:type :vendor_page
             :args {:name_like  {:type 'String}
                    :start      {:type 'Int}
                    :per_page   {:type 'Int}
                    :sort       {:type '(list :sort_item)}}
             :resolve :get-vendor}
    
    :vendor_by_id {:type :vendor
                   :args {:id {:type :id}}
                   :resolve :vendor-by-id}
    :account_for_vendor {:type :account
                         :args {:client_id {:type :id}
                                :vendor_id {:type :id}}
                         :resolve :account-for-vendor}}

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

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

    :add_contact
    {:fields {:id {:type :id}
              :name {:type 'String}
              :email {:type 'String}
              :phone {:type 'String}}}
    :add_address
    {:fields {:id {:type :id}
              :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}
              :plaid_merchant {: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_name {: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_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}
              :invoice_allowance {:type :allowance}
              :vendor_allowance {:type :allowance}
              :numeric_code {:type 'Int}
              :location {:type 'String}
              :account_set {:type 'String}
              :name {:type 'String}
              :client_overrides {:type '(list :edit_account_client_override)}}}}

   :enums {
           :processor {:values [{:enum-value :na}
                                {:enum-value :doordash}
                                {:enum-value :koala}
                                {:enum-value :ezcater}
                                {:enum-value :square}
                                {:enum-value :uber_eats}
                                {:enum-value :grubhub}]}

           :account_allowance {:values [{:enum-value :vendor}
                                        {:enum-value :invoice}]}
           :integration_state {:values [{:enum-value :failed}
                                        {:enum-value :success}
                                        {:enum-value :unauthorized}]}
           :allowance {:values [{:enum-value :allowed}
                                {:enum-value :denied}
                                {:enum-value :warn}
                                {:enum-value :admin_only}]}
           :tin_type {:values [{:enum-value :ein}
                               {:enum-value :ssn}]}
           :type_1099 {:values [{:enum-value :none}
                                {:enum-value :misc}
                                {:enum-value :landlord}]}
           :applicability {:values [{:enum-value :global}
                                    {:enum-value :optional}
                                    {:enum-value :customized}]}
           :role {:values [{:enum-value :none}
                           {:enum-value :user}
                           {:enum-value :manager}
                           {:enum-value :power_user}
                           {:enum-value :admin}]}
           :account_type {:values [{:enum-value :dividend}
                                   {:enum-value :expense}
                                   {:enum-value :asset}
                                   {:enum-value :liability}
                                   {:enum-value :equity}
                                   {:enum-value :revenue}]}}
   :mutations
   {

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

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


(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 get-expense-account-stats [_ {:keys [client_id] } _]
  (let [query (cond-> {:query {:find ['?account '?account-name '(sum ?amount)]
                                :in ['$]
                                :where []}
                        :args [(dc/db conn) 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]]}}))
        result (query2 query)]
    (for [[account-id account-name total] result]
      {:account {:id account-id :name account-name} :total total})))

(defn get-invoice-stats [_ {:keys [client_id] } _]
  (let [query (cond-> {:query {:find ['?name '(sum ?outstanding-balance) '(sum ?total)]
                                :in ['$]
                                :where []}
                        :args [(dc/db conn) 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]
                                                    '(or-join [?d3 ?name]
                                                              (and [(<= ?d3 0)]
                                                                   [(ground :due) ?name])
                                                              (and [(<= ?d3 30)]
                                                                   [(ground :due-30) ?name])
                                                              (and [(<= ?d3 60)]
                                                                   [(ground :due-30) ?name])
                                                              (and [(> ?d3 60)]
                                                                   [(ground :due-later) ?name]))]}}))
        result (->> (query2 query)
                    (group-by first))]
    
    (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 [_ {:keys [client_id]} _]
  (when client_id
    (let [{:client/keys [week-a-credits week-a-debits week-b-credits week-b-debits forecasted-transactions ]} (dc/pull (dc/db conn) '[*] client_id)
          total-cash (reduce
                      (fn [total [credit debit]]
                        (- (+ total credit)
                           debit))
                      0.0
                      (dc/q {:find  '[?debit ?credit]
                             :in    '[$ ?client]
                             :where ['[?client :client/bank-accounts ?ba]
                                     '[?ba :bank-account/type :bank-account-type/check]
                                     '[?je :journal-entry-line/account ?ba]
                                     '[(get-else $ ?je :journal-entry-line/debit 0.0) ?debit]
                                     '[(get-else $ ?je :journal-entry-line/credit 0.0) ?credit]]}
                            (dc/db conn)  client_id))
          bills-due-soon (dc/q {:find  '[?due ?outstanding ?invoice-number ?vendor-id ?vendor-name]
                                :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]
                                        '[?vendor-id :vendor/name ?vendor-name]]}
                               (dc/db conn) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180))))
          outstanding-checks (reduce
                              +
                              0.0
                              (map first (dc/q {: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])]}
                                               (dc/db conn)  client_id (coerce/to-date (t/plus (time/local-now) (t/days 180))))))
          recent-fulfillments (dc/q {:find  '[?f ?d]
                                     :in    '[$ ?client ?min-date]
                                     :where ['[?t :transaction/forecast-match ?f]
                                             '[?t :transaction/client ?client]
                                             '[?t :transaction/date ?d]
                                             '[(>= ?d ?min-date)]]}
                                    (dc/db conn) 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 vendor-name]]
                                  {:due (coerce/to-date-time due)
                                   :invoice_number invoice-number
                                   :vendor {:id vendor-id :name vendor-name}
                                   :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-tracing-resolvers
        {
         :get-all-accounts                 gq-accounts/get-all-graphql
         :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
         :vendor-by-id                     gq-vendors/get-by-id
         :account-for-vendor               gq-accounts/default-for-vendor
         :mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
         :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/upsert-vendor           gq-vendors/upsert-vendor
         :mutation/merge-vendors           gq-vendors/merge-vendors
         :get-vendor                       gq-vendors/get-graphql
         :search-vendor                    gq-vendors/search
         :search-account                   gq-accounts/search})
      gq-checks/attach
      gq-ledger/attach
      gq-plaid/attach
      gq-import-batches/attach
      gq-transactions/attach
      gq-expected-deposit/attach
      gq-ezcater/attach
      gq-invoices/attach
      gq-clients/attach
      gq-sales-orders/attach
      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-name [q]
  (try 
    (str/join "__" (map name (:operations (p/operations (p/parse-query schema q)))))
    (catch Exception _
      "unknown query")))

(defn query
  ([id q]
   (query id q nil))
  ([id q v]
   (statsd/increment "query.graphql.count" {:tags #{(str "query:" (query-name q))}})
   (statsd/time! [(str "query.graphql.time" ) {:tags #{(str "query:" (query-name q))}}]
     (mu/with-context {:query-name (query-name q) :user id :query q}
       (mu/trace ::executing-query
         []
         (try 
           (let [[result time] (time-it (simplify (execute schema q (dissoc v
                                                                            :clients) {:id      id
                                                                                       :clients (:clients v)
                                                                                       :log-context (or (mu/local-context) {})})))]
             
             (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 
                 (alog/warn ::query-validation
                   :exception e)
                 (throw e)
                 #_{:errors [{:message v}]})
               (do 
                 (alog/error ::query-error
                   :exception e)

                 (throw e))))))))))

