(ns auto-ap.datomic.transactions
  (:require
   [auto-ap.datomic
    :refer [add-sorter-fields apply-pagination apply-sort-3 conn merge-query conn pull-many]]
   [auto-ap.datomic.accounts :as d-accounts]
   [auto-ap.graphql.utils :refer [limited-clients]]
   [clj-time.coerce :as coerce]
   [clojure.string :as str]
   [clojure.tools.logging :as log]
   [datomic.client.api :as dc]))

(defn potential-duplicate-ids [db args]
  (when (and (:potential-duplicates args)
           (:bank-account-id args))
    (->> (dc/q '[:find ?tx ?amount ?date
                :in $ ?ba
                :where
                [?tx :transaction/bank-account ?ba]
                [?tx :transaction/amount ?amount]
                [?tx :transaction/date ?date]
                (not [?tx :transaction/approval-status :transaction-approval-status/suppressed])]
              db
              (:bank-account-id args))
         (group-by (fn [[_ amount date]]
                     [amount date]))
         (filter (fn [[_ txes]]
                   (> (count txes) 1)))
         
         (vals)
         (mapcat identity)
         (map first)
         set)))


(defn raw-graphql-ids
  ([args] (raw-graphql-ids (dc/db conn) args))
  ([db args]
   (let [potential-duplicates (potential-duplicate-ids db args)
         query (cond-> {:query {:find []
                                :in ['$ ]
                                :where []}
                        :args [db]} 

                 (:potential-duplicates args)
                 (merge-query {:query {:in '[[?e ...]]}
                               :args [potential-duplicates]})
                 
                 (:exact-match-id args)
                 (merge-query {:query {:in ['?e]
                                       :where []}
                               :args [(:exact-match-id args)]})

                 (limited-clients (:id args))
                 (merge-query {:query {:in ['[?xx ...]]
                                       :where ['[?e :transaction/client ?xx]]}
                               :args [(set (map :db/id (limited-clients (:id args))))]})

                 (:bank-account-id args) 
                 (merge-query {:query {:in ['?bank-account-id]
                                       :where ['[?e :transaction/bank-account ?bank-account-id]]}
                               :args [(:bank-account-id args)]})

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

                 (:account-id args) 
                 (merge-query {:query {:in ['?account-id]
                                       :where ['[?e :transaction/accounts ?accounts]
                                               '[?accounts :transaction-account/account ?account-id]]}
                               :args [(:account-id args)]})

                 (:client-id args) 
                 (merge-query {:query {:in ['?client-id]
                                       :where ['[?e :transaction/client ?client-id]]}
                               :args [(:client-id args)]})

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

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

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

                 (:start (:date-range args)) 
                 (merge-query {:query {:in ['?start-date]
                                       :where ['[?e :transaction/date ?date]
                                               '[(>= ?date ?start-date)]]}
                               :args [(coerce/to-date (:start (:date-range args)))]})

                 (:end (:date-range args)) 
                 (merge-query {:query {:in ['?end-date]
                                       :where ['[?e :transaction/date ?date]
                                               '[(<= ?date ?end-date)]]}
                               :args [(coerce/to-date (:end (:date-range args)))]})

                 (:approval-status args)
                 (merge-query {:query {:in ['?approval-status]
                                       :where ['[?e :transaction/approval-status ?approval-status]]}
                               :args [(:approval-status args)]}) 

                 (:client-code args) 
                 (merge-query {:query {:in ['?client-code]
                                       :where ['[?e :transaction/client ?client-id]
                                               '[?client-id :client/code ?client-code]]}
                               :args [(:client-code args)]})
                 
                 (:original-id args)
                 (merge-query {:query {:in ['?original-id]
                                       :where ['[?e :transaction/client ?c]
                                               '[?c :client/original-id ?original-id]]}
                               :args [(:original-id args)]})

                 (seq (:location args)) 
                 (merge-query {:query {:in ['?location]
                                       :where ['[?e :transaction/accounts ?tas]
                                               '[?tas :transaction-account/location ?location]]}
                               :args [(:location args)]})

                 (:unresolved args) 
                 (merge-query {:query {:where ['[?e :transaction/date]
                                               '(or-join [?e]
                                                 (not [?e :transaction/accounts])
                                                 (and [?e :transaction/accounts ?tas]
                                                      (not [?tas :transaction-account/account])))]}})

                 (:description args) 
                 (merge-query {:query {:in ['?description]
                                       :where ['[?e :transaction/description-original ?do]
                                               '[(clojure.string/lower-case ?do) ?do2]
                                               '[(.contains ?do2 ?description)]]}
                               :args [(clojure.string/lower-case (:description args))]})

                 (:sort args) (add-sorter-fields {"client" ['[?e :transaction/client ?c]
                                                            '[?c :client/name ?sort-client]]
                                                  "account" ['[?e :transaction/date]
                                                             '(or-join [?e ?sort-account]
                                                                       (and [?e :transaction/bank-account ?c]
                                                                            [?c :bank-account/name ?sort-account])
                                                                       (and
                                                                        (not [?e :transaction/bank-account])
                                                                        [(ground "") ?sort-account]))]
                                                  "description-original" ['[?e :transaction/description-original ?sort-description-original]]
                                                  "date" ['[?e :transaction/date ?sort-date]]
                                                  "vendor" ['(or-join [?e ?sort-vendor]
                                                                      (and [(missing? $ ?e :transaction/vendor)]
                                                                           [?e :transaction/description-original ?sort-vendor])
                                                                      (and [?e :transaction/vendor ?v]
                                                                           [?v :vendor/name ?sort-vendor]))]
                                                  "amount" ['[?e :transaction/amount ?sort-amount]]
                                                  "status" ['[?e :transaction/status ?sort-status]]}
                                                 args)
                 true
                 (merge-query {:query {:find ['?sort-default '?e]
                                       :where ['[?e :transaction/id]
                                               '[?e :transaction/date ?sort-default]
                                               '(not [?e :transaction/approval-status :transaction-approval-status/suppressed])]}}))]
     (log/info "query is" query)
     (cond->> query
       true (dc/q)
       true (apply-sort-3 (assoc args :default-asc? false))
       true (apply-pagination args)))))

(defn graphql-results [ids db _]
  (let [results (->> (pull-many db '[* {:transaction/client           [:client/name :db/id :client/code]
                                        :transaction/approval-status  [:db/ident :db/id]
                                        :transaction/bank-account     [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
                                        :transaction/forecast-match   [:db/id :forecasted-transaction/identifier]
                                        :transaction/vendor           [:db/id :vendor/name]
                                        :transaction/matched-rule     [:db/id :transaction-rule/note]
                                        :transaction/payment          [:db/id :payment/date]
                                        :transaction/expected-deposit [:db/id :expected-deposit/date]
                                        :transaction/accounts         [:transaction-account/amount
                                                               :db/id
                                                               :transaction-account/location
                                                                       {:transaction-account/account [:account/name :db/id
                                                                                                      :account/location
                                                                                                      {:account/client-overrides [:account-client-override/name
                                                                                                                                  {:account-client-override/client [:db/id]}]}]}]
                                        :transaction/yodlee-merchant  [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]}]
                                ids)
                     (map #(update % :transaction/date coerce/from-date))
                     (map #(update % :transaction/post-date coerce/from-date))
                     (map #(update % :transaction/accounts
                                   (fn [tas]
                                     (map
                                       (fn [ta]
                                         (update ta :transaction-account/account d-accounts/clientize (:db/id (:transaction/client %))))
                                       tas))))
                     (map (fn [transaction]
                            (cond-> transaction
                              (:transaction/payment transaction) (update-in [:transaction/payment :payment/date] coerce/from-date)
                              (:transaction/expected-deposit transaction) (update-in [:transaction/expected-deposit :expected-deposit/date] coerce/from-date))
                            ))
                     (map #(dissoc % :transaction/id))
                     (group-by :db/id))]
    
    (->> ids
         (map results)
         (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 
    (->> {:query {:find ['?e]
                  :in ['$ '[?e ...]]
                  :where ['[?e :transaction/date]]}
          :args [(dc/db conn) ids]}
         (dc/q)
         (map first)
         vec)
    []))

(defn get-by-id [id]
  (-> 
   (dc/pull (dc/db conn)
           '[* {:transaction/client [:client/name :db/id :client/code :client/locations]
                :transaction/approval-status [:db/ident :db/id]
                :transaction/bank-account [:bank-account/name :bank-account/code :bank-account/yodlee-account-id :db/id :bank-account/locations :bank-account/current-balance]
                :transaction/vendor [:db/id :vendor/name]
                :transaction/matched-rule [:db/id :transaction-rule/note]
                :transaction/forecast-match [:db/id :forecasted-transaction/identifier]
                :transaction/accounts [:transaction-account/amount
                                       :db/id
                                       :transaction-account/location
                                       { :transaction-account/account [:account/name :db/id
                                                                       :account/location
                                                                       {:account/client-overrides [:account-client-override/name
                                                                                                   {:account-client-override/client [:db/id]}]}]}]
                :transaction/yodlee-merchant [:db/id :yodlee-merchant/yodlee-id :yodlee-merchant/name]}]
           id)
   (update :transaction/date coerce/from-date)
   (update :transaction/post-date coerce/from-date)
   (dissoc :transaction/id)))

