(ns auto-ap.rule-matching
  (:require [auto-ap.logging :as alog]
            [iol-ion.query :refer [->pattern]]
            [iol-ion.tx :refer [random-tempid]]))

(defn rule-applies? [transaction {:keys [:transaction-rule/description
                                         :transaction-rule/client-group
                                         :transaction-rule/dom-gte :transaction-rule/dom-lte
                                         :transaction-rule/amount-gte :transaction-rule/amount-lte
                                         :transaction-rule/client :transaction-rule/bank-account
                                         :transaction-rule/yodlee-merchant]} ]
  (let [transaction-dom (some-> transaction
                            :transaction/date
                            .toInstant
                            (.atZone (java.time.ZoneId/of "US/Pacific"))
                            (.get java.time.temporal.ChronoField/DAY_OF_MONTH))]
    (and
     (if description
       (re-find description (or (:transaction/description-original transaction) ""))
       true)
     (if dom-gte
       (>= transaction-dom dom-gte)
       true)
     (if dom-lte
       (<= transaction-dom dom-lte)
       true)
     (if amount-gte
       (>= (:transaction/amount transaction) amount-gte)
       true)
     (if amount-lte
       (<= (:transaction/amount transaction) amount-lte)
       true)
     (if client-group
       (do
         (println "CHECKING HERE" client-group)
         ((set (-> transaction :transaction/client :client/groups)) client-group))
       true)
     (if client
       (or
        (= (:transaction/client transaction)
           (:db/id client))
        (= (:db/id (:transaction/client transaction))
           (:db/id client)))
       true)
     (if yodlee-merchant
       (= (:yodlee-merchant/yodlee-id (:transaction/yodlee-merchant transaction))
          (:yodlee-merchant/yodlee-id yodlee-merchant))
       true)
     (if bank-account
       (or
        (= (:db/id (:transaction/bank-account transaction))
           (:db/id bank-account))
        (= (:transaction/bank-account transaction)
           (:db/id bank-account)))
       true))))

(defn rule-priority [rule]
  (or 
   (->> [[:transaction-rule/bank-account 0]
         [:transaction-rule/client 1]
         [:transaction-rule/client-group 2]
         [:transaction-rule/dom-lte 3]
         [:transaction-rule/dom-gte 4] 
         [:transaction-rule/amount-lte 4]
         [:transaction-rule/amount-gte 4] 
         [:transaction-rule/description 5]
         [:transaction-rule/yodlee-merchant 6]]
        (filter (fn [[key]]
                  (get rule key)))
        (map second)
        first)
   6))

(defn get-matching-rules-by-priority [rules-by-priority transaction]
  (loop [[rule-set & rules] rules-by-priority]
    (if rule-set 
      (let [matching-rules (into [] (filter #(rule-applies? transaction %) rule-set))]
        (if (seq matching-rules)
          matching-rules
          (recur rules)))
      [])))



(defn group-rules-by-priority [rules]
  (->> rules
       (map (fn [r] (update r :transaction-rule/description #(some-> % ->pattern))))
       (group-by rule-priority)
       (sort-by first)
       (map second)))

(defn get-matching-rules [transaction all-rules]
  (->> all-rules
       (map (fn [r] (update r :transaction-rule/description #(some-> % ->pattern))))
       (filter #(rule-applies? transaction %))))

(defn spread-cents [cents n]
  (let [default-spread (for [_ (range n)]
                         (int (* cents (/ 1.0 n))))
        short-by (- cents (reduce + 0 default-spread)) ;; amount that was lost in the differenc
        adjusted-spread (map
                         (fn [cents increments]
                           (+ cents increments))

                         default-spread
                         (concat (take short-by (repeat 1)) (repeat 0)))]
    (filter #(> % 0) adjusted-spread)))

(defn apply-rule [transaction rule valid-locations]
  (with-precision 2
    (let [accounts (vec (mapcat
                         (fn [tra]
                           (let [cents-to-distribute (int (Math/round (Math/abs (* (:transaction-rule-account/percentage tra)
                                                                                   (:transaction/amount transaction)
                                                                                   100))))]
                             (if (= "Shared" (:transaction-rule-account/location tra))
                               (->> valid-locations
                                    (map
                                     (fn [cents location]
                                       {:db/id (random-tempid)
                                        :transaction-account/account (:db/id (:transaction-rule-account/account tra))
                                        :transaction-account/amount (* 0.01 cents)
                                        :transaction-account/location location})
                                     (spread-cents cents-to-distribute (count valid-locations))))
                               [(cond-> {:db/id (random-tempid)
                                         :transaction-account/account (:db/id (:transaction-rule-account/account tra))
                                         :transaction-account/amount (* 0.01 cents-to-distribute)}
                                  (:transaction-rule-account/location tra) (assoc :transaction-account/location (:transaction-rule-account/location tra)))])))
                         (filter (comp seq :transaction-rule-account/account) (:transaction-rule/accounts rule))))
          accounts (mapv
                    (fn [a]
                      (update a :transaction-account/amount
                              #(with-precision 2
                                 (double (.setScale (bigdec %) 2 java.math.RoundingMode/HALF_UP)))))
                    accounts)
          leftover (with-precision 2 (.round (bigdec (- (Math/abs (:transaction/amount transaction))
                                                        (Math/abs (reduce + 0.0 (map #(:transaction-account/amount %) accounts)))))
                                             *math-context*))
          accounts (if (seq accounts)
                     (update-in accounts [(dec (count accounts)) :transaction-account/amount] #(+ % (double leftover)))
                     [])]
      (assoc transaction
             :transaction/matched-rule (:db/id rule)
             :transaction/approval-status (:transaction-rule/transaction-approval-status rule)
             :transaction/accounts accounts
             :transaction/vendor (:db/id (:transaction-rule/vendor rule))))))

(defn rule-applying-fn [rules]
  (let [rules-by-priority (group-rules-by-priority rules)]
    (fn [transaction valid-locations]
      (if (:transaction/payment transaction)
        transaction
        (let [matching-rules (get-matching-rules-by-priority rules-by-priority transaction )]
          (if-let [top-match (and (= (count matching-rules) 1) (first matching-rules))]
            (apply-rule transaction top-match valid-locations)
            transaction))))))
