(ns auto-ap.graphql.plaid
  (:require
   [auto-ap.datomic
    :refer [add-sorter-fields
            apply-pagination
            apply-sort-3
            conn
            merge-query
            pull-attr
            pull-many-by-id]]
   [auto-ap.graphql.utils
    :refer [->graphql
            <-graphql
            assert-admin
            assert-can-see-client
            assert-present
            attach-tracing-resolvers
            limited-clients]]
   [auto-ap.plaid.core :as p]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]
   [clojure.tools.logging :as log]
   [datomic.client.api :as dc]))

(defn plaid-link-token [context value _]
  (when-not (:client_id value)
    (throw (ex-info "Client ID is required" {:validation-error "Client ID is required"})))
  (assert-can-see-client (:id context) (:client_id value))
  (let [client-code (pull-attr (dc/db conn) :client/code (:client_id value))]
    {:token (p/get-link-token client-code)}))

(defn link-plaid [context value _]
  (when-not (:client_code value)
    (throw (ex-info "Client not provided" {:validation-error "Client not provided."})))
  (when-not (:public_token value)
    (throw (ex-info "Public token not provided" {:validation-error "public token not provided"})))
  (log/info (:id context) (pull-attr (dc/db conn) :db/id [:client/code (:client_code value)]))
  (assert-can-see-client (:id context) (pull-attr (dc/db conn) :db/id [:client/code (:client_code value)]))
  (let [access-token (:access_token (p/exchange-public-token (:public_token value) (:client_code value)))
        account-result (p/get-accounts access-token )
        item {:plaid-item/client [:client/code (:client_code value)]
              :plaid-item/external-id (-> account-result :item :item_id )
              :plaid-item/access-token access-token
              :plaid-item/status (or (some-> account-result :item :error)
                                        "SUCCESS")
              :plaid-item/last-updated (coerce/to-date (time/now))
              :db/id "plaid-item"}]

    (dc/transact conn {:tx-data
                       (->> (:accounts account-result)
                            (map (fn [a]
                                   (let [balance (some-> a :balances :current (* 0.01))]
                                     (cond-> {:plaid-account/external-id (:account_id a)
                                              :plaid-account/number (:mask a)
                                              :plaid-account/name (str (:name a) " " (:mask a))
                                              :plaid-item/_accounts "plaid-item"}
                                       balance (assoc :plaid-account/balance balance)))))
                            (into [item]))})
    (log/info "Access token was " access-token)
    {:message (str "Plaid linked successfully.")}))


(def default-read '[:db/id
                    :plaid-item/external-id
                    :plaid-item/last-updated
                    :plaid-item/status
                    {:plaid-item/accounts [:db/id
                                           :plaid-account/external-id
                                           :plaid-account/number
                                           :plaid-account/balance
                                           :plaid-account/name]}])

(defn raw-graphql-ids [db args]
  (let [query (cond-> {:query {:find []
                               :in ['$]
                               :where []}
                       :args [db]}

                (:sort args) (add-sorter-fields {"external-id" ['[?e :plaid-item/external-id ?sort-external-id]]}
                                                args)

                (limited-clients (:id args))
                (merge-query {:query {:in    ['[?xx ...]]
                                      :where ['[?e :plaid-item/client ?xx]]}
                              :args  [ (set (map :db/id (limited-clients (:id args))))]})

                (:client-id args)
                (merge-query {:query {:in '[?client-id]
                                      :where ['[?e :plaid-item/client ?client-id]]}
                              :args [(:client-id args)]})

                true
                (merge-query {:query {:find ['?e]
                                      :where ['[?e :plaid-item/external-id]]}}))]

    (cond->> query
      true (dc/q)
      true (apply-sort-3 args)
      true (apply-pagination args))))

(defn graphql-results [ids db _]
  (let [results (pull-many-by-id db default-read ids)]
    (->> ids
         (map results))))

(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 get-plaid-item-page [context args _]
    
  (let [args (assoc args :id (:id context))
        [plaid-items cnt] (get-graphql (<-graphql (assoc args :id (:id context))))]
    {:plaid_items (->> plaid-items
                       (map #(update % :plaid-item/last-updated coerce/from-date))
                       (map ->graphql))
     :total cnt
     :count (count plaid-items)
     :start (:start args 0)
     :end (+ (:start args 0) (count plaid-items))}))

(defn delete-plaid-item [context args _]
  (assert-admin (:id context))
  (assert-present args :id)
  (dc/transact conn {:tx-data [[:db/retractEntity (:id args)]]})
  {:message "Item deleted."})

(defn attach [schema]
  (-> 
   (merge-with merge schema
               {:objects {:plaid_link_result
                          {:fields {:token {:type 'String}} }

                          :plaid_item
                          {:fields {:external_id {:type 'String}
                                    :id {:type :id}
                                    :client {:type :client}
                                    :status {:type 'String}
                                    :last_updated {:type :iso_date}
                                    :accounts {:type '(list :plaid_account)}}}

                          :plaid_item_page {:fields {:plaid_items {:type '(list :plaid_item)}
                                                     :count {:type 'Int}
                                                     :total {:type 'Int}
                                                     :start {:type 'Int}
                                                     :end {:type 'Int}}}

                          :plaid_account
                          {:fields {:external_id {:type 'String}
                                    :id {:type :id}
                                    :balance {:type :money}
                                    :name {:type 'String}
                                    :number {:type 'String}}}}
                :queries {:plaid_link_token {:type :plaid_link_result
                                             :args {:client_id {:type :id}}
                                             :resolve :plaid-link-token}
                          :plaid_item_page {:type :plaid_item_page
                                            :args {:client_id {:type :id}
                                                   :sort {:type '(list :sort_item)}
                                                   :start {:type 'Int}
                                                   :per_page {:type 'Int}}
                                            :resolve :get-plaid-item-page}}
                :mutations {:link_plaid {:type :message
                                         :args {:client_code {:type 'String}
                                                :public_token {:type 'String}}
                                         :resolve :mutation/link-plaid}
                            :delete_plaid_item {:type :message
                                                :args {:id {:type :id}}
                                                :resolve :mutation/delete-plaid-item}}})
   (attach-tracing-resolvers {:plaid-link-token           plaid-link-token
                              :get-plaid-item-page        get-plaid-item-page
                              :mutation/link-plaid        link-plaid
                              :mutation/delete-plaid-item delete-plaid-item})))

