(ns auto-ap.import.plaid
  (:require [auto-ap.datomic :refer [conn random-tempid]]
            [auto-ap.import.common :refer [wrap-integration]]
            [auto-ap.import.transactions :as t]
            [auto-ap.logging :as alog]
            [auto-ap.plaid.core :as p]
            [auto-ap.solr]
            [auto-ap.time :as atime]
            [auto-ap.utils :refer [allow-once by]]
            [clj-time.coerce :as coerce]
            [clj-time.core :as time]
            [clojure.string :as str]
            [datomic.api :as dc]
            [digest :as di]
            [iol-ion.utils :refer [remove-nils]]
            [manifold.deferred :as de]
            [manifold.executor :as ex]))

(defn get-plaid-accounts 
  ([db]
   (-> (dc/q '[:find ?ba ?c ?external-id ?t
               :in $
               :where
               [?c :client/bank-accounts ?ba]
               [?ba :bank-account/plaid-account ?pa]
               [?pa :plaid-account/external-id ?external-id]
               [?pi :plaid-item/accounts ?pa]
               [?pi :plaid-item/access-token ?t]]
             db)))
  ([db & client-codes]
   (-> (dc/q '[:find ?ba ?c ?external-id ?t
               :in $ [?cc ...]
               :where
               [?c :client/code ?cc]
               [?c :client/bank-accounts ?ba]
               [?ba :bank-account/plaid-account ?pa]
               [?pa :plaid-account/external-id ?external-id]
               [?pi :plaid-item/accounts ?pa]
               [?pi :plaid-item/access-token ?t]]
             db
             client-codes))))


(defn plaid->transaction [t plaid-merchant->vendor-id]
  (alog/info ::trying-transaction :transaction t)
  (cond-> #:transaction {:description-original (:name t)
                         :raw-id (:transaction_id t)
                         :db/id (random-tempid)
                         :id #_{:clj-kondo/ignore [:unresolved-var]}
                         (di/sha-256 (:transaction_id t))
                         :amount (if (= "credit" (:type (:account t)))
                                   (- (double (:amount t)))
                                   (- (double (:amount t))))
                         :date (coerce/to-date (atime/parse (or (:authorized_date t) (:date t)) atime/iso-date))
                         :status "POSTED"}
    (:merchant_name t) (assoc :transaction/plaid-merchant {:plaid-merchant/name (:merchant_name t)
                                                           :db/id (random-tempid)})
    (not (str/blank? (:check_number t))) (assoc :transaction/check-number (Long/parseLong (:check_number t)))
    #_#_(plaid-merchant->vendor-id (:merchant_name t)) (assoc :transaction/default-vendor
                                                          (plaid-merchant->vendor-id (:merchant_name t)))))

(defn build-plaid-merchant->vendor-id []
  (into {}
        (dc/q '[:find ?pmn ?v
                :in $
                :where
                [?v :vendor/plaid-merchant ?pm]
                [?pm :plaid-merchant/name ?pmn]]
              (dc/db conn ))))


(def single-thread (ex/fixed-thread-executor 1))

(defn rebuild-search-index []
  (de/future-with
    single-thread
    (auto-ap.solr/index-documents-raw
      auto-ap.solr/impl
      "plaid_merchants"
      (for [[result] (dc/qseq {:query '[:find (pull ?v [:plaid-merchant/name :db/id])
                                        :in $
                                        :where [?v :plaid-merchant/name]]
                               :args  [(dc/db conn)]})]
        {"id"   (:db/id result)
         "name" (:plaid-merchant/name result)}))))

(defn upsert-accounts []
  (try
    (doseq [[bank-account-id client-id external-id access-token] (get-plaid-accounts (dc/db conn))]
      (try
        (let [accounts (p/get-accounts access-token)
              item (p/get-item access-token)]
          @(dc/transact
            conn
            (for [a (:accounts accounts)]
              (remove-nils
               {:plaid-account/external-id (:account_id a)
                :plaid-account/last-synced (coerce/to-date (coerce/to-date-time (-> item :status :transactions :last_successful_update))) :plaid-account/balance (or (some-> a
                                                   :balances
                                                   :current
                                                   double)
                                           0.0)}))))
        (catch Exception e
          (alog/warn ::couldnt-upsert-account :error e))))


    (catch Exception e
      (alog/warn ::couldnt-upsert-accounts :error e))))




(defn import-plaid-int []
  (let [_ (upsert-accounts)
        import-batch              (t/start-import-batch :import-source/plaid "Automated plaid user")
        end                       (atime/local-now)
        start                     (time/plus end (time/days -30))
        plaid-merchant->vendor-id (build-plaid-merchant->vendor-id)]
    (try
      (doseq [[bank-account-id client-id external-id access-token] (get-plaid-accounts (dc/db conn))
              :let                                                 [transaction-result (wrap-integration #(p/get-transactions access-token external-id start end)
                                                         bank-account-id)
                    accounts-by-id (by  :account_id (:accounts transaction-result))]
              transaction                                          (:transactions transaction-result)]
        (when (not (:pending transaction))
          (t/import-transaction! import-batch (assoc (plaid->transaction (assoc transaction
                                                                                :account
                                                                                (accounts-by-id (:account_id transaction)))
                                                                         plaid-merchant->vendor-id)
                                                     :transaction/bank-account bank-account-id
                                                     :transaction/client client-id))))
      (try
        (rebuild-search-index)
        (catch Exception e
          (alog/error ::cant-index-plaid
                      :error e)))
      (t/finish! import-batch)
      (catch Exception e
        (t/fail! import-batch e)))))

(def import-plaid (allow-once import-plaid-int))


