(ns iol-ion.query
  (:require [clj-time.core :as time]
            [clj-time.coerce :as coerce]
            [clj-time.format :as f]
            [datomic.api :as dc]))

;; TODO WILL NOT WORK IN DATOMIC CLOUD
(defn entid [db i]
  (dc/entid db i))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn dollars-0? [amt]
  (< -0.001 amt  0.001))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn dollars= [amt1 amt2]
  (dollars-0? (- amt1 amt2) ))

(defn localize [d]
  (time/to-time-zone d (time/time-zone-for-id "America/Los_Angeles")))

(defn local-now []
  (localize (time/now)))


(defn recent-date
  ([]
   (recent-date 90))
  ([n]
   (coerce/to-date (time/minus (local-now) (time/days n)))))

(def excel-formatter (f/with-zone (f/formatter "MM/dd/yyyy") (time/time-zone-for-id "America/Los_Angeles")))
(defn excel-date [d]
  (->> d
      (coerce/to-date-time)
      localize
      (f/unparse excel-formatter )))

(def iso-formatter (f/with-zone (f/formatter "yyyy-MM-dd") (time/time-zone-for-id "America/Los_Angeles")))
(defn iso-date [d]
  (->> d
      (coerce/to-date-time)
      localize
      (f/unparse iso-formatter )))

(defn sales-orders-in-range [db client start end]
  (let [end (or end #inst "2050-01-01")]
    (into #{}
          (map (fn [datom]
                 (:e datom)))
          (dc/index-range db
                          :sales-order/client+date
                          [client start]
                          [client end]))))




(defn can-see-client? [identity client]
  (when (not client)
    (println "WARNING - permission checking for null client"))
  (or (= "admin" (:user/role identity))
      ((set (map :db/id (:user/clients identity))) (:db/id client))
      ((set (map :db/id (:user/clients identity))) client)))


(defn ->pattern [x]
  (. java.util.regex.Pattern (compile x java.util.regex.Pattern/CASE_INSENSITIVE)))


(defn dom [^java.util.Date x]
  (-> x
      (.toInstant)
      (.atZone (java.time.ZoneId/of "US/Pacific"))
      (.get  java.time.temporal.ChronoField/DAY_OF_MONTH)))

(defn next-day [d]
  (some-> d
          coerce/to-date-time
          (time/plus (time/days 1))
          coerce/to-date))

(defn scan-sales-orders [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :sales-order/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-charges [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :charge/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-sales-refunds [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :sales-refund/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-expected-deposits [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :expected-deposit/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-cash-drawer-shifts [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :cash-drawer-shift/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-invoices [db clients start end]
  (for [c clients
        :let [c (entid db c)]
        r  (seq (dc/index-range db
                                :invoice/client+date
                                [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-transactions [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                :transaction/client+date
                                [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-ledger [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :journal-entry/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn scan-payments [db clients start end]
  (for [c    clients
        :let [c (entid db c)]
        r    (seq (dc/index-range db
                                  :payment/client+date
                                  [c (or start #inst "2001-01-01T08:00:00.000-00:00") ]
                                  [c (or (next-day end) #inst "2030-03-05T08:00:00.000-00:00") ]))]
    [(:e r) (first (:v r)) (second (:v r))]))

(defn ident [x]
  (:db/ident x))

(deftype Line [^Long id ^Long client-id ^Long account-id ^String location ^java.util.Date date ^Double debit ^Double credit ^Double running-balance]
  )

(defmethod print-method Line [entity writer]
  (.write writer (format "Line %d: client:%d account:%d location:%s date:%s"
                         (.-id entity)
                         (.-client-id entity)
                         (.-account-id entity)
                         (.-location entity)
                         (iso-date (.-date entity)))))


(defn ->line [{[current-client current-account current-location current-date debit credit running-balance]
               :v
               id :e}]
  (Line. id current-client current-account current-location current-date debit credit running-balance)
  )

(defn compare-account [^Line l1 ^Line l2] 
  
  (let [a (compare (.-date l1) (.-date l2))]
    (if (not= 0 a)
      a 
      (compare (.-id l1) (.-id l2)))))

(defn account-sets [db client-id]
  (for [running-balance-set (->>  (dc/index-range db :journal-entry-line/running-balance-tuple [client-id] [(inc client-id)])
                                  (seq)
                                  (map ->line)
                                  (partition-by (fn set-partition [^Line l]
                                                  [(.-account-id l) (.-location l)]))) ]
    (->> running-balance-set
         (sort compare-account))))

(defn account-snapshot [db client-id ^java.util.Date end]
  (for [running-balance-set (account-sets db client-id)
        :let [z
              (->> running-balance-set
                   (take-while (fn until-date [^Line l]
                                 (let [^java.util.Date d (.-date l)]
                                   (<= (.compareTo ^java.util.Date d end) 0))))
                   last) ]
        :when (and z (.-id z))]
    [(.-client-id z) (.-account-id z) (.-location z) (.-date z) (.-running-balance z)]))

#_(doseq [[ n] (dc/q '[:find ?cd :where [?c :client/code ?cd] [?c :client/groups "NTG"]] (dc/db auto-ap.datomic/conn))]
  (println n)
  (dc/q  '[:find ?code ?name ?afc ?an  ?l ?d2 ?balance
           :in $ ?end ?group
           :where
           [(clj-time.coerce/to-date-time ?end) ?end2]
           [(iol-ion.query/localize ?end2) ?end3]
           [(clj-time.coerce/to-date ?end3) ?end4]
           (or 
            [?c :client/groups ?group]
            [?c :client/code ?group]) 
           [?c :client/name ?name]
           [?c :client/code ?code]
           [?c :client/bank-accounts ?b] 
           [(iol-ion.query/account-snapshot $ ?c ?end4) [?x ...]]
           [(untuple ?x) [_ ?a ?l ?date ?balance]]
           [(not= nil ?a)]
           [(iol-ion.query/excel-date ?date) ?d2]
           (or-join [?a ?afc ?an]
                    (and [?a :account/name ?an]
                         [?a :account/numeric-code ?afc])
                    (and [?a :bank-account/name ?an]
                         [?a :bank-account/numeric-code ?afc]))]
         (dc/db auto-ap.datomic/conn)
         #inst "2024-10-10" n))

(defn detailed-account-snapshot
  ([db client-id ^java.util.Date end]
   (detailed-account-snapshot db client-id end nil))
  ([db client-id ^java.util.Date end ^java.util.Date start]

   (for [running-balance-set (account-sets db client-id)
         :let [{:keys [account-id client-id location current-balance debits credits count sample]}
               (->> running-balance-set
                    (filter (fn until-end [^Line l]
                              (let [^java.util.Date d (.-date l)]
                                (and
                                 (if start
                                   (>= (.compareTo ^java.util.Date d start) 0)
                                   true)
                                 (<= (.compareTo ^java.util.Date d end) 0)))))
                    (reduce
                     (fn step [acc ^Line l]
                       (assoc! acc
                               :count (unchecked-inc (:count acc))
                               :debits (unchecked-add (:debits acc) (or (.-debit l) 0.0))
                               :credits (unchecked-add  (:credits acc) (or (.-credit l) 0.0))
                               :current-balance (.-running-balance l)
                               :account-id (.-account-id l)
                               :client-id (.-client-id l)
                               :location (.-location l)
                               :sample (.-id l)))
                     (transient {:count 0
                                 :debits 0.0
                                 :credits 0.0
                                 :current-balance 0.0})))]
         :when client-id]
     (do 
       [client-id account-id location debits credits current-balance count sample]))))


(comment 
  (->> 
   (detailed-account-snapshot (dc/db auto-ap.datomic/conn)
                              (auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
                                                       [:client/code "NGOP"])
                              #inst "2024-05-01")
   (map #(nth % 2))
   (into #{})
   seq)

(account-snapshot (dc/db auto-ap.datomic/conn) 
                  (auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
                                           [:client/code "NGOP"])
                  #inst "2022-01-01")

(def orig (->> [:client/code "NGOP"]
               (auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn))
               (account-sets (dc/db auto-ap.datomic/conn))
               (mapcat (fn [ls] 
                         ls))
               (filter (fn [l] (nil? (.-location l))))
               (into #{})))

(.-location orig)

(def orig (into [] (take 5000 (mapcat (fn [ls] 
                                        (map #(.-id %) ls)) (account-sets (dc/db auto-ap.datomic/conn) 
                                                                          (auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
                                                                                                   [:client/code "NGOP"]))))))

  (def n (into [] (take 5000 (mapcat (fn [ls] 
                                        (map #(.-id %) ls)) (account-sets (dc/db auto-ap.datomic/conn) 
                                                                          (auto-ap.datomic/pull-id (dc/db auto-ap.datomic/conn)
                                                                                                   [:client/code "NGOP"]))))))
  
  (= orig n)

#_(seq (dc/q '[:find  ?c ?a ?l ?date ?balance
             :in $ 
             :where [?c :client/code "NGOP"]
             [(iol-ion.query/account-snapshot $ ?c #inst "2023-01-01") [?x ...]]
             [(untuple ?x) [_ ?a ?l ?date ?balance]]]
           (dc/db auto-ap.datomic/conn)))

#_(->> (seq (dc/q '[:find ?code ?name ?afc ?an  ?l ?d2 ?balance ?end4
                  :in $ ?end ?group
                  :where
                  [(clj-time.coerce/to-date-time ?end) ?end2]
                  [(iol-ion.query/localize ?end2) ?end3]
                  [(clj-time.coerce/to-date ?end3) ?end4]
                  (or
                   [?c :client/groups ?group]
                   [?c :client/code ?group])
                  [?c :client/name ?name]
                  [?c :client/code ?code]
                  [?c :client/bank-accounts ?b]
                  [(iol-ion.query/account-snapshot $ ?c ?end4) [?x ...]]
                  [(untuple ?x) [_ ?a ?l ?date ?balance]]
                  [(iol-ion.query/excel-date ?date) ?d2]
                  [(not= nil ?a)]
                  (or-join [?a ?afc ?an]
                           (and [?a :account/name ?an]
                                [?a :account/numeric-code ?afc])
                           (and [?a :bank-account/name ?an]
                                [?a :bank-account/numeric-code ?afc]))]
                (dc/db auto-ap.datomic/conn)
                #inst "2024-09-23"
                "NGKG"))
                (filter (fn [[_ _ afc]]
                          (= 12990 afc)))
                (map (fn [[_ _ _ _ _ _ a]]
                       (Math/round a)))))