(ns auto-ap.jobs.register-invoice-import
  (:require
   [amazonica.aws.s3 :as s3]
   [auto-ap.datomic :refer [audit-transact conn pull-attr]]
   [auto-ap.jobs.core :refer [execute]]
   [auto-ap.time :as atime]
   [auto-ap.utils :refer [dollars=]]
   [clj-time.coerce :as coerce]
   [auto-ap.logging :as alog]
   [clojure.data.csv :as csv]
   [clojure.java.io :as io]
   [clojure.string :as str]
   [config.core :refer [env]]
   [datomic.api :as dc])
  (:import
   (java.util UUID)))

(def bucket (:data-bucket env))

(defn s3->csv [url]
  (try 
    (->> (-> (s3/get-object {:bucket-name bucket
                             :key         (str "bulk-import/" url)})
             :input-stream
             io/reader
             csv/read-csv))
    (catch Exception e
      (alog/error
        :file-not-found
        :error e
        :url url)
      (throw e))))

(defn register-invoice-import* [data]
  (let [db             (dc/db conn)
        i->invoice-id  (fn [i]
                         (try (Long/parseLong i)
                              (catch Exception _
                                (pull-attr db :db/id [:invoice/original-id (Long/parseLong (first (str/split i #"-")))]))))
        invoice-totals (->> data
                            (drop 1)
                            (group-by first)
                            (map (fn [[k values]]
                                   [(i->invoice-id k)
                                    (reduce + 0.0
                                            (->> values
                                                 (map (fn [[_ _ _ _ amount]]
                                                        (- (Double/parseDouble amount))))))
                                    ]))
                            (into {}))]
    (->> 
      (for [[i
             invoice-expense-account-id
             target-account
             target-date
             amount
             _
             location] (drop 1 data)
            :let       [invoice-id (i->invoice-id i)

                        invoice (dc/pull db '[*] invoice-id)
                        current-total (:invoice/total invoice)
                        target-total (invoice-totals invoice-id) ;; TODO should include expense accounts not visible
                        new-account? (not (boolean (or (some-> invoice-expense-account-id not-empty Long/parseLong)
                                                       (:db/id (first (:invoice/expense-accounts invoice))))))

                        invoice-expense-account-id (or (some-> invoice-expense-account-id not-empty Long/parseLong)
                                                       (:db/id (first (:invoice/expense-accounts invoice)))
                                                       (str (UUID/randomUUID)))
                        invoice-expense-account (when-not new-account?
                                                  (or (dc/pull db '[*] invoice-expense-account-id)
                                                      (dc/pull db '[*] [:invoice-expense-account/original-id invoice-expense-account-id])))
                        current-account-id (:db/id (:invoice-expense-account/account invoice-expense-account)) 
                        target-account-id (Long/parseLong (str/trim target-account))

                        target-date (coerce/to-date (atime/parse target-date atime/normal-date))
                        current-date (:invoice/date invoice)
                        

                        current-expense-account-amount (:invoice-expense-account/amount invoice-expense-account 0.0) 
                        target-expense-account-amount (- (Double/parseDouble amount))


                        current-expense-account-location (:invoice-expense-account/location invoice-expense-account) 
                        target-expense-account-location location


                        [[_ _ invoice-payment]] (vec (dc/q
                                                       '[:find ?p ?a ?ip
                                                         :in $ ?i
                                                         :where [?ip :invoice-payment/invoice ?i]
                                                         [?ip :invoice-payment/amount ?a]
                                                         [?ip :invoice-payment/payment ?p]
                                                         ]
                                                       db invoice-id))]
            :when current-total]

        [
         (when (not (dollars= current-total target-total))
           {:db/id         invoice-id
            :invoice/total target-total})

         (when (and (not (dollars= 0.0 target-total))
                    (= :invoice-status/voided (:db/ident (:invoice/status invoice))))
           {:db/id         invoice-id
            :invoice/total target-total
            :invoice/status :invoice-status/paid})

         (when new-account?
           {:db/id                    invoice-id
            :invoice/expense-accounts invoice-expense-account-id})

         (when (and target-date (not= current-date target-date))
           {:db/id        invoice-id
            :invoice/date target-date})

         (when (and
                 (not (dollars= current-total target-total))
                 invoice-payment)
           [:db/retractEntity invoice-payment])

         (when (or new-account?
                   (not (dollars= current-expense-account-amount target-expense-account-amount)))
           {:db/id                          invoice-expense-account-id
            :invoice-expense-account/amount target-expense-account-amount})

         (when (not= current-expense-account-location
                     target-expense-account-location)
           {:db/id                            invoice-expense-account-id
            :invoice-expense-account/location target-expense-account-location})

         (when (not= current-account-id target-account-id )
           {:db/id                           invoice-expense-account-id
            :invoice-expense-account/account target-account-id})])
      (mapcat identity)
      (filter identity)
      vec)))

(defn register-invoice-import [args]
  (let [{:keys [invoice-url]} args
        data                 (s3->csv invoice-url)]
    (alog/info ::rows
      :count (count data))
    (doseq [n (partition-all 50 (register-invoice-import* data))]
      (alog/info ::transacting
        :count (count n)
        :sample (take 2 n))
      (audit-transact n {:user/name "register-invoice-import"
                         :user/role "admin"}))))

(defn -main [& _]
  (execute "register-invoice-import" #(register-invoice-import (:args env))))
