(ns auto-ap.datomic.transactions
  (:require
   [auto-ap.datomic
    :refer [add-sorter-fields
            apply-pagination
            apply-sort-3
            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]
   [clojure.string :as str]
   [datomic.api :as dc]
   [clj-time.core :as time]))

(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 [valid-clients (extract-client-ids (:clients args)
                                           (:client-id args)
                                           (when (:client-code args)
                                             [:client/code (:client-code args)]))
         potential-duplicates (potential-duplicate-ids db args)
         query
         (if (:exact-match-id args)
           (cond-> {:query {:find  '[?e]
                            :in    '[$ ?e [?c ...]]
                            :where '[[?e :transaction/client ?c]]}
                    :args  [db
                            (:exact-match-id args)
                            valid-clients]})
           (cond-> {:query {:find []
                            :in '[$ [?clients ?start ?end]]
                            :where '[[(iol-ion.query/scan-transactions $ ?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)]]}

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

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

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

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

             (= (:linked-to args) :payment)
             (merge-query {:query {:where ['[?e :transaction/payment]]}})

             (= (:linked-to args) :expected-deposit)
             (merge-query {:query {:where ['[?e :transaction/expected-deposit]]}})

             (= (:linked-to args) :invoice)
             (merge-query {:query {:where ['[?e :transaction/payment ?p]
                                           '[_ :invoice-payment/payment ?p]]}})

             (= (:linked-to args) :none)
             (merge-query {:query {:where ['(not [?e :transaction/payment])
                                           '(not [?e :transaction/expected-deposit])]}})

             (: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]
                                           '(not [?e :transaction/approval-status :transaction-approval-status/suppressed])]}})))]
     (cond->> (observable-query query)
       true (apply-sort-3 (assoc args :default-asc? false))
       true (apply-pagination args)))))

(defn is-locked? [transaction]
  (let [transaction-date (some-> transaction :transaction/date coerce/to-date-time)
        bank-account-start-date (some-> transaction :transaction/bank-account :bank-account/start-date coerce/to-date-time)
        client-locked-until (some-> transaction :transaction/client :client/locked-until coerce/to-date-time)
        locked-by-client? (cond (not transaction-date) false
                                (not client-locked-until) false
                                :else (time/before? transaction-date client-locked-until))
        locked-by-bank-account? (cond (not transaction-date) false
                                      (not bank-account-start-date) false
                                      :else (time/before? transaction-date bank-account-start-date))]
    (or locked-by-bank-account? locked-by-client?)))

(defn graphql-results [ids db _]
  (let [results (->> (pull-many db '[* {:transaction/client           [:client/name :db/id :client/code :client/locked-until]
                                        :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 :bank-account/start-date]
                                        :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]
                                        :transaction/plaid-merchant  [:db/id :plaid-merchant/name]}]
                                ids)
                     (map #(assoc % :transaction/is-locked (is-locked? %)))
                     (map #(update % :transaction/date coerce/from-date))
                     (map #(update % :transaction/post-date coerce/from-date))
                     (map (fn [t]
                            (let [cn (:transaction/check-number t)]
                              (if (and cn (nat-int? cn) (> cn Integer/MAX_VALUE))
                                (dissoc t :transaction/check-number)
                                t))))
                     (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
    (->> (dc/q {:find  ['?e]
                :in    ['$ '[?e ...]]
                :where ['[?e :transaction/date]]}
               (dc/db conn) ids)
         (map first)
         vec)
    []))

(defn get-by-id [id]
  (->
   (dc/pull (dc/db conn)
            '[* {:transaction/client [:client/name :db/id :client/code :client/locations :client/groups]
                 :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]
                 :transaction/plaid-merchant [:db/id :plaid-merchant/name]}]
            id)
   (update :transaction/date coerce/from-date)
   (update :transaction/post-date coerce/from-date)
   (dissoc :transaction/id)))

