(ns auto-ap.datomic.ledger
  (:require
   [auto-ap.datomic
    :refer [add-sorter-fields
            apply-pagination
            apply-sort-4
            conn
            merge-query
            observable-query
            pull-many]]
   [auto-ap.datomic.accounts :as d-accounts]
   [auto-ap.graphql.utils :refer [extract-client-ids]]
   [clj-time.coerce :as coerce]
   [datomic.api :as dc]))

(defn raw-graphql-ids [db args]
  (let [valid-clients (extract-client-ids (:clients args)
                                          (:client-id args)
                                          (when (:client-code args)
                                            [:client/code (:client-code args)]))
        query
        (if (:exact-match-id args)
          {:query {:find  '[?e]
                   :in    '[$ ?e [?c ...]]
                   :where '[[?e :journal-entry/client ?c]]}
           :args  [db
                   (:exact-match-id args)
                   valid-clients]}
          (cond-> {:query {:find []
                           :in ['$ '[?clients ?start ?end]]
                           :where '[[(iol-ion.query/scan-ledger $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]}
                   :args [db
                          [valid-clients
                           (some-> (:start (:date-range args)) coerce/to-date)
                           (some-> (:end (:date-range args)) coerce/to-date)]]}

            (:only-external args) 
            (merge-query {:query {:where ['(not [?e :journal-entry/original-entity ])]}})

            (seq (:external-id-like args))
            (merge-query {:query {:in    ['?external-id-like]
                                  :where ['[?e :journal-entry/external-id ?external-id]
                                          '[(.contains ^String ?external-id ?external-id-like)]]}
                          :args  [(:external-id-like args)]})

            (seq (:source args))
            (merge-query {:query {:in    ['?source]
                                  :where ['[?e :journal-entry/source ?source]]}
                          :args  [(:source args)]})

            (:vendor-id args) 
            (merge-query {:query {:in    ['?vendor-id]
                                  :where ['[?e :journal-entry/vendor ?vendor-id]]}
                          :args  [(:vendor-id args)]})


            (or (seq (:numeric-code args))
                (:bank-account-id args)
                (not-empty (:location args)))
            (merge-query {:query {:where ['[?e :journal-entry/line-items ?li]]}})

            (seq (:numeric-code args))
            (merge-query {:query {:in    ['[[?from-numeric-code ?to-numeric-code]]]
                                  :where ['[?li :journal-entry-line/account ?a]
                                          '(or-join [?a ?c]
                                                    [?a :account/numeric-code ?c]
                                                    [?a :bank-account/numeric-code ?c])
                                          '[(>= ?c ?from-numeric-code)]
                                          '[(<= ?c ?to-numeric-code)]]}
                          :args  [(vec (for [{:keys [from to]} (:numeric-code args)]
                                         [(or from 0) (or to 99999)]))]})


            (:amount-gte args) 
            (merge-query {:query {:in    ['?amount-gte]
                                  :where ['[?e :journal-entry/amount ?a]
                                          '[(>= ?a ?amount-gte)]]}
                          :args  [(:amount-gte args)]})

            (:amount-lte args) 
            (merge-query {:query {:in    ['?amount-lte]
                                  :where ['[?e :journal-entry/amount ?a]
                                          '[(<= ?a ?amount-lte)]]}
                          :args  [(:amount-lte args)]})

            (:bank-account-id args) 
            (merge-query {:query {:in    ['?a]
                                  :where ['[?li :journal-entry-line/account ?a]]}
                          :args  [(:bank-account-id args)]})

            (:account-id args) 
            (merge-query {:query {:in    ['?a2]
                                  :where ['[?e :journal-entry/line-items ?li2]
                                          '[?li2 :journal-entry-line/account ?a2]]}
                          :args  [(:account-id args)]})

            (not-empty (:location args)) 
            (merge-query {:query {:in    ['?location]
                                  :where ['[?li :journal-entry-line/location ?location]]}
                          :args  [(:location args)]})

            (not-empty (:locations args)) 
            (merge-query {:query {:in    ['[?location ...]]
                                  :where ['[?li :journal-entry-line/location ?location]]}
                          :args  [(:locations args)]})

            (:sort args) (add-sorter-fields {"client"      ['[?e :journal-entry/client ?c]
                                                            '[?c :client/name ?sort-client]]
                                             "date"        ['[?e :journal-entry/date ?sort-date]]
                                             "vendor"      ['[?e :journal-entry/vendor ?v]
                                                            '[?v :vendor/name ?sort-vendor]]
                                             "amount"      ['[?e :journal-entry/amount ?sort-amount]]
                                             "external-id" ['[?e :journal-entry/external-id ?sort-external-id]]
                                             "source"      ['[?e :journal-entry/source ?sort-source]]}
                                            args)

            true
            (merge-query {:query {:find ['?sort-default '?e]}})))]
    (->> (observable-query query)
         (apply-sort-4 (assoc args :default-asc? true))
      (apply-pagination args))))

(defn graphql-results [ids db _]
  (let [results (->> (pull-many db '[* {:journal-entry/client     [:client/name :client/code :db/id]
                                        :journal-entry/vendor     [:vendor/name :db/id]
                                        :journal-entry/line-items [* {:journal-entry-line/account [*
                                                                                                   {:account/type [*]}
                                                                                                   {:account/client-overrides [:account-client-override/name
                                                                                                                               {:account-client-override/client [:db/id]}]}
                                                                                                   {:bank-account/type [*]}]}]}]
                                ids)
                     (map #(update % :journal-entry/date coerce/from-date))
                     (map (fn [je]
                            (update je :journal-entry/line-items
                                    (fn [jels]
                                      (map
                                        #(update % :journal-entry-line/account d-accounts/clientize (:db/id (:journal-entry/client je)))
                                        jels)))))
                     (filter (fn [je]
                               (every?
                                 (fn [jel]
                                   (let [include-in-reports (-> jel :journal-entry-line/account :bank-account/include-in-reports)]
                                     (or (nil? include-in-reports)
                                         (true? include-in-reports))))
                                 (:journal-entry/line-items je))))
                     (group-by :db/id))]
    (->> ids
         (map results)
         (filter identity)
         (map first))))

(defn get-graphql [args]
  (let [db (dc/db conn)
        {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)]
    [(->> (graphql-results ids-to-retrieve db args))
     matching-count]))

(defn filter-ids [ids]
  (if ids 
    (->> (dc/q {:find  ['?e]
                :in    ['$ '[?e ...]]
                :where ['[?e :journal-entry/date]]}
               (dc/db conn) ids)
         (map first)
         vec)
    []))
