(ns iol-ion.tx.upsert-transaction
  (:require [datomic.api :as dc]))

(defn -remove-nils [m]
  (let [result (reduce-kv
                (fn [m k v]
                  (if (not (nil? v))
                    (assoc m k v)
                    m))
                {}
                m)]
    (if (seq result)
      result
      nil)))

(defn transaction->journal-entry
  [db transaction-id raw-transaction-id]
  (let [entity            (dc/pull db [:transaction/client
                                       :transaction/date
                                       :transaction/description-original
                                       :db/id
                                       :transaction/vendor
                                       :transaction/amount
                                       :transaction/cleared-against
                                       {:transaction/accounts        [:transaction-account/account
                                                                      :transaction-account/location
                                                                      :transaction-account/amount]
                                        :transaction/approval-status [:db/ident]
                                        :transaction/bank-account    [:db/id {:bank-account/type [:db/ident]}]}]
                                   transaction-id)
        decreasing?       (< (or (:transaction/amount entity) 0.0) 0.0)
        credit-from-bank? decreasing?
        debit-from-bank?  (not decreasing?)]
    (when (and (not (= :transaction-approval-status/excluded (:db/ident (:transaction/approval-status entity))))
               (not (= :transaction-approval-status/suppressed (:db/ident (:transaction/approval-status entity))))
               (:transaction/amount entity)
               (not (< -0.001 (:transaction/amount entity) 0.001)))
      (-remove-nils
       {:journal-entry/source                "transaction"
        :journal-entry/client                (:db/id (:transaction/client entity))
        :journal-entry/date                  (:transaction/date entity)
        :journal-entry/original-entity       raw-transaction-id
        :journal-entry/alternate-description (:transaction/description-original entity)
        :journal-entry/vendor                (:db/id (:transaction/vendor entity))
        :journal-entry/amount                (Math/abs (:transaction/amount entity))
        :journal-entry/cleared-against       (:transaction/cleared-against entity)

        :journal-entry/line-items (into [(-remove-nils {:journal-entry-line/account  (:db/id (:transaction/bank-account entity))
                                                        :db/id                       (str raw-transaction-id "-" 0)
                                                        :journal-entry-line/location "A"
                                                        :journal-entry-line/credit   (when credit-from-bank?
                                                                                       (Math/abs (:transaction/amount entity)))
                                                        :journal-entry-line/debit    (when debit-from-bank?
                                                                                       (Math/abs (:transaction/amount entity)))})]
                                        (map-indexed
                                         (fn [i a]
                                           (-remove-nils {:db/id                       (str raw-transaction-id "-" (inc i))
                                                          :journal-entry-line/account  (:db/id (:transaction-account/account a))
                                                          :journal-entry-line/location (:transaction-account/location a)
                                                          :journal-entry-line/debit    (when credit-from-bank?
                                                                                         (Math/abs (:transaction-account/amount a)))
                                                          :journal-entry-line/credit   (when debit-from-bank?
                                                                                         (Math/abs (:transaction-account/amount a)))}))
                                         (if (seq (:transaction/accounts entity))
                                           (:transaction/accounts entity)
                                           [{:transaction-account/amount (:transaction/amount entity)}])))

        :journal-entry/cleared true}))))

(defn upsert-transaction [db transaction]
  ;; because some transactions will reference temp ids, you have to dissoc them, like :transaction/payment
  (let [upserted-entity  [[:upsert-entity (dissoc transaction :transaction/payment :import-batch/_entry)]]
        with-transaction (dc/with  db upserted-entity)
        transaction-id   (or (-> with-transaction :tempids (get (:db/id transaction)))
                             (:db/id transaction))
        journal-entry    (transaction->journal-entry (:db-after with-transaction)
                                                     transaction-id
                                                     (:db/id transaction))]
    (into [[:upsert-entity transaction]]
          (if journal-entry
            [[:upsert-ledger journal-entry]]
            [[:db/retractEntity [:journal-entry/original-entity (:db/id transaction)]]]))))


#_(comment

  ;; If transactions are failing, it is likely that there are multiple bank accounts linked
  ;; to yodlee or plaid. here is how i debugged
  (upsert-transaction (dc/db auto-ap.datomic/conn) {:transaction/matched-rule 17592233159891,
                                                    :db/id "34411061-4656-4e77-8cc0-2f2769b4324c",
                                                    :transaction/status "POSTED",
                                                    :transaction/description-original "Rotten Robbie #03",
                                                    :transaction/approval-status {:db/id 17592231963877,
                                                                                  :db/ident :transaction-approval-status/approved},
                                                    :transaction/plaid-merchant {:db/id "223ceae4-d9e7-4e7f-92be-4fb00676088b",
                                                                                 :plaid-merchant/name "Rotten Robbie"},
                                                    :transaction/bank-account 17592232681223,
                                                    :transaction/vendor 17592232627053,
                                                    :transaction/date #inst "2024-02-24T08:00:00Z",
                                                    :transaction/client 17592232577980,
                                                    :transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996",
                                                    :transaction/amount -84.43,
                                                    :transaction/accounts [{:db/id "cad8463f-2dfe-47dc-ab17-831e87a633d5",
                                                                            :transaction-account/account 17592231963549,
                                                                            :transaction-account/location "CB",
                                                                            :transaction-account/amount 84.43}],
                                                    :transaction/raw-id "gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP"})

  ["upsert-transaction"]
  (user/init-repl)

  (def my-transaction {:transaction/bank-account 17592232681223,
                       :transaction/date #inst "2024-02-24T08:00:00.000-00:00",
                       :transaction/matched-rule 17592233159891,
                       :transaction/client 17592232577980,
                       :transaction/status "POSTED",
                       :transaction/plaid-merchant
                       {:plaid-merchant/name "Rotten Robbie", :db/id "b2776792-9e2b-46e8-a9c8-bf80abea359e"},
                       :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc",
                       :transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996",
                       :transaction/description-original "Rotten Robbie #03",
                       :transaction/approval-status {:db/id 17592231963877, :db/ident :transaction-approval-status/approved}, :transaction/amount -84.43,
                       :transaction/accounts [{:db/id "c402c7b3-c11b-484b-b670-bd48f79a3e5f", :transaction-account/account 17592231963549, :transaction-account/amount 84.43, :transaction-account/location "CB"}],
                       :transaction/raw-id "gQypbv5946F08op74wZmidDg8qD8Q1fM6gEBP",
                       :transaction/vendor 17592232627053})

  (def my-journal {:journal-entry/alternate-description "Rotten Robbie #03",
                   :journal-entry/date #inst "2024-02-24T08:00:00.000-00:00",
                   :journal-entry/original-entity "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc",
                   :journal-entry/client 17592232577980,
                   :journal-entry/line-items [{:journal-entry-line/credit 84.43, :journal-entry-line/account 17592232681223, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-0", :journal-entry-line/location "A"}
                                              {:journal-entry-line/account 17592231963549, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-1", :journal-entry-line/debit 84.43, :journal-entry-line/location "CB"}
                                              {:journal-entry-line/account 17592231963549, :db/id "ac2efd80-bb03-48b2-b0d0-6b47a5c119dc-2", :journal-entry-line/debit 84.43, :journal-entry-line/location "CB"}],
                   :journal-entry/source "transaction",
                   :journal-entry/cleared true,
                   :journal-entry/amount 84.43,
                   :journal-entry/vendor 17592232627053})
  (dc/pull (dc/db auto-ap.datomic/conn) '[*] [:transaction/id "11a4a13e713d63f476009027e9a53e217e13d0192a37df8ab96c0eed4bdbe996"])
  wl
  (user/init-repl)

  (or (when-let [original-entity (:journal-entry/original-entity my-journal)]
        (dc/pull (dc/db auto-ap.datomic/conn) iol-ion.tx.upsert-ledger/extant-read [:journal-entry/original-entity original-entity]))
      (when-let [external-id (:journal-entry/external-id my-journal)]
        (dc/pull (dc/db auto-ap.datomic/conn) iol-ion.tx.upsert-ledger/extant-read [:journal-entry/external-id external-id])))

  @(dc/transact auto-ap.datomic/conn [[:upsert-entity my-transaction]
                                      [:upsert-ledger my-journal]])

  (auto-ap.datomic/pull-attr (dc/db auto-ap.datomic/conn) :bank-account/code  17592232681223)
  (auto-ap.datomic/pull-attr (dc/db auto-ap.datomic/conn) :bank-account/code  17592232681228)

  )