(ns iol-ion.tx.upsert-invoice
  (:import [java.util Date])
  (: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 invoice->journal-entry
  [db invoice-id raw-invoice-id]
  (let [entity          (dc/pull db
                                 '[:invoice/total
                                   :invoice/exclude-from-ledger
                                   :invoice/outstanding-balance
                                   :invoice/date
                                   {:invoice/vendor           [:db/id :vendor/name]
                                    :invoice/client           [:db/id :client/code]
                                    :invoice/payment          [:db/id {:payment/status [:db/ident]}]
                                    :invoice/status           [:db/ident]
                                    :invoice/import-status    [:db/ident]
                                    :invoice/expense-accounts [:invoice-expense-account/account
                                                               :invoice-expense-account/amount
                                                               :invoice-expense-account/location]}]
                                 invoice-id)
        credit-invoice? (< (:invoice/total entity 0.0) 0.0)]
    (when-not (or
                (not (:invoice/total entity))
                (= true (:invoice/exclude-from-ledger entity))
                (= :import-status/pending (:db/ident (:invoice/import-status entity)))
                (= :invoice-status/voided (:db/ident (:invoice/status entity)))
                (< -0.001 (:invoice/total entity) 0.001))
      
      (-remove-nils 
        {:journal-entry/source          "invoice"
         :journal-entry/client          (:db/id (:invoice/client entity))
         :journal-entry/date            (:invoice/date entity)
         :journal-entry/original-entity raw-invoice-id
         :journal-entry/vendor          (:db/id (:invoice/vendor entity))
         :journal-entry/amount          (Math/abs (:invoice/total entity))

         :journal-entry/line-items (into [(cond-> {:db/id (str raw-invoice-id "-" 0)
                                                   :journal-entry-line/account :account/accounts-payable
                                                   :journal-entry-line/location "A"
                                                   }
                                            credit-invoice?       (assoc :journal-entry-line/debit (Math/abs (:invoice/total entity)))
                                            (not credit-invoice?) (assoc :journal-entry-line/credit (Math/abs (:invoice/total entity))))]
                                         (map-indexed (fn [i ea]
                                                        (cond-> 
                                                            {:db/id (str raw-invoice-id "-" (inc i))
                                                             :journal-entry-line/account (:db/id (:invoice-expense-account/account ea))
                                                             :journal-entry-line/location (or (:invoice-expense-account/location ea) "HQ")
                                                             }
                                                          credit-invoice?       (assoc :journal-entry-line/credit (Math/abs (:invoice-expense-account/amount ea)))
                                                          (not credit-invoice?) (assoc :journal-entry-line/debit (Math/abs (:invoice-expense-account/amount ea)))))
                                                      (:invoice/expense-accounts entity)))
         :journal-entry/cleared    (and (< (:invoice/outstanding-balance entity) 0.01)
                                        (every? #(= :payment-status/cleared (:payment/status %)) (:invoice/payments entity))
                                        )}))))

(defn current-date [db]
  (let [ last-tx (dc/t->tx (dc/basis-t db))
        [[date]] (seq (dc/q '[:find ?ti :in $ ?tx
                              :where [?tx :db/txInstant ?ti]]
                            db
                            last-tx))]
    date))

(defn upsert-invoice [db invoice]
  (let [upserted-entity [[:upsert-entity invoice]]
        with-invoice    (dc/with  db upserted-entity)
        invoice-id      (or (-> with-invoice :tempids (get (:db/id invoice)))
                            (:db/id invoice))
        journal-entry   (invoice->journal-entry (:db-after with-invoice)
                                              invoice-id
                                              (:db/id invoice))
        client-id (-> (dc/pull (:db-after with-invoice) 
                               [{:invoice/client [:db/id]}] 
                               invoice-id)
                      :invoice/client 
                      :db/id)]
    (into upserted-entity
          (if journal-entry 
            [[:upsert-ledger journal-entry]]
            [[:db/retractEntity [:journal-entry/original-entity (:db/id invoice)]]
             {:db/id client-id
              :client/ledger-last-change (current-date db)}]))))
