(ns auto-ap.graphql.transaction-rules
  (:require
   [auto-ap.datomic :refer [audit-transact conn merge-query query2]]
   [auto-ap.datomic.transaction-rules :as tr]
   [auto-ap.datomic.transactions :as d-transactions]
   [auto-ap.graphql.utils
    :refer [->graphql
            <-graphql
            assert-admin
            ident->enum-f
            limited-clients
            result->page
            snake->kebab]]
   [auto-ap.rule-matching :as rm]
   [auto-ap.utils :refer [dollars=]]
   [clj-time.coerce :as c]
   [clojure.string :as str]
   [com.brunobonacci.mulog :as mu]
   [datomic.api :as dc]
   [iol-ion.tx :refer [random-tempid]]))

(defn get-transaction-rule-page [context args _]
  (let [args (assoc args :clients (:clients context))
        [journal-entries journal-entries-count] (tr/get-graphql (<-graphql args))]
    (result->page (->> journal-entries
                       (map (ident->enum-f :transaction-rule/transaction-approval-status)))
                  journal-entries-count :transaction_rules args)))

(defn get-transaction-rule-matches [context args _]
  (if (= "admin" (:user/role (:id context)))
    (let [transaction (update  (d-transactions/get-by-id (:transaction_id args)) :transaction/date c/to-date)
          all-rules   (tr/get-all-for-client (:db/id (:transaction/client transaction)))

          ]
      (mu/log ::counted
              :count (count all-rules))
      (doto (map ->graphql (rm/get-matching-rules transaction all-rules)) (#(println (count %)))))
    nil))

(defn transaction-rule-account->entity [{:keys [id account_id percentage location]}]
  #:transaction-rule-account {:percentage percentage
                              :db/id      (or id (random-tempid))
                              :account    account_id
                              :location   location})

(defn delete-transaction-rule [context {:keys [transaction_rule_id ]} _]
  (assert-admin (:id context))
  (let [existing-transaction-rule (tr/get-by-id transaction_rule_id)]
    (when-not (:transaction-rule/description existing-transaction-rule)
      (throw (ex-info "Transaction rule not found" {:validation-error "Transaction Rule not found"})))

    (audit-transact [[:db/retractEntity transaction_rule_id]] (:id context))
    transaction_rule_id))

(defn upsert-transaction-rule [context {{:keys [id description yodlee_merchant_id note client_id bank_account_id amount_lte amount_gte vendor_id accounts transaction_approval_status dom_gte dom_lte]} :transaction_rule} _]
  (assert-admin (:id context))
  (let [account-total (reduce + 0 (map (fn [x] (:percentage x)) accounts))
        _ (try
            (. java.util.regex.Pattern (compile description java.util.regex.Pattern/CASE_INSENSITIVE))
            (catch Exception e
              (throw (ex-info (ex-message e) {:validation-error (ex-message e)}))))
        _ (when-not (dollars= 1.0 account-total) 
            (let [error (str "Account total (" account-total ") does not reach 100%")]
              (throw (ex-info error {:validation-error error}))))
        _ (when (and (str/blank? description)
                     (nil? yodlee_merchant_id)) 
            (let [error (str "You must provide a description or a yodlee merchant")]
              (throw (ex-info error {:validation-error error}))))
        _ (doseq [a accounts
                  :let [{:keys [:account/location :account/name]} (dc/pull (dc/db conn) [:account/location :account/name] (:account_id a))
                        client (dc/pull (dc/db conn) [:client/locations] client_id) 
                        ]]
            (when (and location (not= location (:location a)))
              (let [err (str "Account " name " uses location " (:location a) ", but is supposed to be " location)]
                (throw (ex-info err {:validation-error err}) )))
            
            (when (and (not location)
                       (not (get (into #{"Shared"} (:client/locations client))
                                 (:location a))))
              (let [err (str "Account " name " uses location " (:location a) ", but doesn't belong to the client.")]
                (throw (ex-info err {:validation-error err}) ))))
        rule-id (if id
                  id
                  "transaction-rule")
        transaction [[:upsert-entity #:transaction-rule {:db/id                     (or rule-id (random-tempid))
                                                          :description               description
                                                          :note                      note
                                                          :client                    client_id
                                                          :bank-account              bank_account_id
                                                          :yodlee-merchant           yodlee_merchant_id
                                                          :dom-lte                   dom_lte
                                                          :dom-gte                   dom_gte
                                                          :amount-lte                amount_lte
                                                          :amount-gte                amount_gte
                                                          :vendor                    vendor_id
                                                          :transaction-approval-status
                                                          (some->> transaction_approval_status
                                                                   name
                                                                   snake->kebab
                                                                   (keyword "transaction-approval-status"))
                                                          :transaction-rule/accounts (map transaction-rule-account->entity accounts)}]]


        transaction-result (audit-transact  transaction (:id context))]
    (-> (tr/get-by-id (or (-> transaction-result :tempids (get "transaction-rule"))
                                           id))
        ((ident->enum-f :transaction-rule/transaction-approval-status))
        (->graphql))))

(defn -test-transaction-rule [id {:keys [:transaction-rule/description :transaction-rule/client :transaction-rule/bank-account :transaction-rule/amount-lte :transaction-rule/amount-gte :transaction-rule/dom-lte :transaction-rule/dom-gte :transaction-rule/yodlee-merchant]} include-coded? count]
  (let [query (cond-> {:query {:find ['(pull ?e [* {:transaction/client [:client/name]
                                                    :transaction/bank-account [:bank-account/name]
                                                    :transaction/payment [:db/id]}
                                                 ])]
                               :in ['$ ]
                               :where []}
                       :args [(dc/db conn)]} 
                description
                (merge-query {:query {:in    ['?descr]
                                      :where ['[(iol-ion.query/->pattern ?descr) ?description-regex]]}
                              :args  [description]})

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

                bank-account
                (merge-query {:query {:in    ['?bank-account-id]
                                      :where ['[?e :transaction/bank-account ?bank-account-id]]}
                              :args  [(:db/id bank-account)]})

                description
                (merge-query {:query {:where ['[?e :transaction/description-original ?do]
                                              '[(re-find ?description-regex ?do)]]}})

                yodlee-merchant
                (merge-query {:query {:in    ['?yodlee-merchant-id]
                                      :where ['[?e :transaction/yodlee-merchant ?yodlee-merchant-id]]}
                              :args  [(:db/id yodlee-merchant)]})

                amount-gte
                (merge-query {:query {:in    ['?amount-gte]
                                      :where ['[?e :transaction/amount ?ta]
                                              '[(>= ?ta ?amount-gte)]]}
                              :args  [amount-gte]})

                amount-lte
                (merge-query {:query {:in    ['?amount-lte]
                                      :where ['[?e :transaction/amount ?ta]
                                              '[(<= ?ta ?amount-lte)]]}
                              :args  [amount-lte]})

                dom-lte
                (merge-query {:query {:in    ['?dom-lte]
                                      :where ['[?e :transaction/date ?transaction-date]
                                              '[(iol-ion.query/dom ?transaction-date) ?dom]
                                              '[(<= ?dom ?dom-lte)]]}
                              :args  [dom-lte]})

                dom-gte
                (merge-query {:query {:in    ['?dom-gte]
                                      :where ['[?e :transaction/date ?transaction-date]
                                              '[(iol-ion.query/dom ?transaction-date) ?dom]
                                              '[(>= ?dom ?dom-gte)]]}
                              :args  [dom-gte]})

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


                (not include-coded?)
                (merge-query {:query {:where ['[or [?e :transaction/approval-status :transaction-approval-status/unapproved]
                                                [(missing? $ ?e :transaction/approval-status)]]]}})

                true
                (merge-query {:query {:where ['[?e :transaction/id]]}}))]
    (->> 
      (query2 query)
      (transduce (comp
                   (take (or count 15))
                   (map first)
                   (map #(dissoc % :transaction/id))
                   (map (fn [x]
                          (update x :transaction/date c/from-date)))
                   (map ->graphql))
                 conj []))))

(defn test-transaction-rule [{:keys [id]} {{:keys [description client_id bank_account_id amount_lte amount_gte dom_lte dom_gte yodlee_merchant_id]} :transaction_rule} _]
  (assert-admin id)
  (-test-transaction-rule id #:transaction-rule {:description description
                                                 :client (when client_id {:db/id client_id})
                                                 :bank-account (when bank_account_id {:db/id bank_account_id})
                                                 :amount-lte amount_lte
                                                 :amount-gte amount_gte
                                                 :dom-lte dom_lte
                                                 :dom-gte dom_gte
                                                 :yodlee-merchant (when yodlee_merchant_id {:db/id yodlee_merchant_id})}
                          true 15))


(defn run-transaction-rule [{:keys [id]} {:keys [transaction_rule_id count]} _]
  (assert-admin id)
  (-test-transaction-rule id (tr/get-by-id transaction_rule_id) false count))
