(ns auto-ap.datomic.invoices
  (:require
   [auto-ap.datomic
    :refer [add-sorter-fields
            apply-pagination
            query2
            observable-query
            apply-sort-3
            conn
            merge-query
            pull-many]]
   [auto-ap.datomic.accounts :as d-accounts]
   [auto-ap.datomic.vendors :as d-vendors]
   [auto-ap.graphql.utils :refer [limited-clients extract-client-ids]]
   [auto-ap.time-utils :refer [next-dom]]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]
   [com.brunobonacci.mulog :as mu]
   [clojure.set :refer [rename-keys]]
   [datomic.api :as dc]
   [iol-ion.tx :refer [random-tempid]]))

(def default-read '[*
                    {:invoice/client [:client/name :db/id :client/locations :client/code]}
                    {:invoice/vendor [* {:vendor/address [*]}]}
                    {:invoice/status [:db/ident]}
                    {:invoice/expense-accounts [* {:invoice-expense-account/account [:account/name :db/id
                                                                                     :account/location
                                                                                     {:account/client-overrides [:account-client-override/name
                                                                                                                 {:account-client-override/client [:db/id]}]}]}]}
                    {:invoice-payment/_invoice [* {:invoice-payment/payment [* {:payment/status [*]}
                                                                             {:payment/bank-account [*]}
                                                                             {:transaction/_payment [*]}]}]}])

(defn <-datomic [x]
  (-> x
       (update  :invoice/date coerce/from-date)
       (update  :invoice/due coerce/from-date)
       (update  :invoice/scheduled-payment coerce/from-date)
       (update  :invoice/status :db/ident)
       (update :invoice/expense-accounts (fn [eas]
                                           (map 
                                             #(update % :invoice-expense-account/account d-accounts/clientize (:db/id (:invoice/client x)))
                                             eas)))
       (rename-keys  {:invoice-payment/_invoice :invoice/payments})))


(defn raw-graphql-ids
  ([args]
   (raw-graphql-ids (dc/db conn) args))
  ([db args]
   (let [valid-clients (extract-client-ids (:clients args)
                                           (:client-id args)
                                           (when (:client-code args)
                                             [:client/code (:client-code args)]))
         query
         (if (:exact-match-id args)
           {:query {:find  '[?e]
                    :in    '[$ ?e [?c ...]]
                    :where '[[?e :invoice/client ?c]]}
            :args  [db
                    (:exact-match-id args)
                    valid-clients]}
           (cond-> {:query {:find []
                            :in '[$ [?clients ?start ?end]]
                            :where '[
                                     [(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
                                     ]}
                    :args [db
                           [valid-clients
                            (some-> (:start (:date-range args)) coerce/to-date)
                            (some-> (:end (:date-range args)) coerce/to-date)]]}

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

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

             (:original-id args)
             (merge-query {:query {:in    ['?original-id]
                                   :where [
                                           '[?e :invoice/client ?c]
                                           '[?c :client/original-id ?original-id]]}
                           :args  [ (cond-> (:original-id args)
                                      (string? (:original-id args)) Long/parseLong )]}) 

             

             

             (:start (:due-range args)) (merge-query {:query {:in    '[?start-due]
                                                              :where ['[?e :invoice/due ?due]
                                                                      '[(>= ?due ?start-due)]]}
                                                      :args  [(coerce/to-date (:start (:due-range args)))]})

             (:end (:due-range args)) (merge-query {:query {:in    '[?end-due]
                                                            :where ['[?e :invoice/due ?due]
                                                                    '[(<= ?due ?end-due)]]}
                                                    :args  [(coerce/to-date (:end (:due-range args)))]})
             

             (:import-status args)
             (merge-query {:query {:in    ['?import-status]
                                   :where ['[?e :invoice/import-status ?import-status]]}
                           :args  [ (keyword "import-status" (:import-status args))]})
             (:status args)
             (merge-query {:query {:in    ['?status]
                                   :where ['[?e :invoice/status ?status]]}
                           :args  [ (:status args)]})
             (:vendor-id args)
             (merge-query {:query {:in    ['?vendor-id]
                                   :where ['[?e :invoice/vendor ?vendor-id]]}
                           :args  [ (:vendor-id args)]})

             (:account-id args)
             (merge-query {:query {:in    ['?account-id]
                                   :where ['[?e :invoice/expense-accounts ?iea ?]
                                           '[?iea :invoice-expense-account/account ?account-id]]}
                           :args  [ (:account-id args)]})

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

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

             (seq (:invoice-number-like args))
             (merge-query {:query {:in    ['?invoice-number-like]
                                   :where ['[?e :invoice/invoice-number ?invoice-number]
                                           '[(.contains ^String ?invoice-number ?invoice-number-like)]]}
                           :args  [(:invoice-number-like args)]})

             (:scheduled-payments args)
             (merge-query {:query {:in    []
                                   :where ['[?e :invoice/scheduled-payment]]}
                           :args  []})

             (:unresolved args)
             (merge-query {:query {:in    []
                                   :where ['(or-join [?e]
                                                     (not [?e :invoice/expense-accounts ])
                                                     (and [?e :invoice/expense-accounts ?ea]
                                                          (not [?ea :invoice-expense-account/account])))]}
                           :args  []})

             (seq (:location args))
             (merge-query {:query {:in    ['?location]
                                   :where ['[?e :invoice/expense-accounts ?eas]
                                           '[?eas :invoice-expense-account/location ?location]]}
                           :args  [(:location args)]})

             (:sort args) (add-sorter-fields {"client"               ['[?e :invoice/client ?c]
                                                                      '[?c :client/name ?sort-client]]
                                              "vendor"               ['[?e :invoice/vendor ?v]
                                                                      '[?v :vendor/name ?sort-vendor]] 
                                              "description-original" ['[?e :transaction/description-original ?sort-description-original]]
                                              "location"             ['[?e :invoice/expense-accounts ?iea]
                                                                      '[?iea :invoice-expense-account/location ?sort-location]]
                                              "date"                 ['[?e :invoice/date ?sort-date]]
                                              "due"                  ['[(get-else $ ?e :invoice/due #inst "2050-01-01") ?sort-due]]
                                              "invoice-number"       ['[?e :invoice/invoice-number ?sort-invoice-number]]
                                              "total"                ['[?e :invoice/total ?sort-total]]
                                              "outstanding-balance"  ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]}
                                             args)
             true
             (merge-query {:query {:find  ['?sort-default '?e ]}}) ))]
     (->> (observable-query query)
       (apply-sort-3 args)
       (apply-pagination args)))))


(defn graphql-results [ids db _]
  (let [results (->> (pull-many db  default-read ids)
                     (group-by :db/id))
        
        invoices (->> ids
                      (map results)
                      (map first)
                      (mapv <-datomic))]
    invoices))

(defn sum-outstanding [ids]
  
  (->> 
    (dc/q {:find  ['?id '?o]
           :in    ['$ '[?id ...]]
           :where ['[?id :invoice/outstanding-balance ?o]]}
          (dc/db conn)
          ids)
   (map last)
   (reduce
    +
    0.0)))

(defn sum-total-amount [ids]
  
  (->> 
    (dc/q {:find  ['?id '?o]
           :in    ['$ '[?id ...]]
           :where ['[?id :invoice/total ?o]]
           }
          (dc/db conn)
          ids)
    (map last)
    (reduce
      +
      0.0)))

(defn get-graphql [args]
  
  (let [db                                           (dc/db conn)
        {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)
        outstanding                                  (sum-outstanding ids-to-retrieve)
        total-amount                                 (sum-total-amount ids-to-retrieve)]

    
    [(->> (graphql-results ids-to-retrieve db args))
     matching-count
     outstanding
     total-amount]))

(defn get-by-id [id]
  (-> (dc/db conn)
      (dc/pull  default-read id)
      (<-datomic)))

(defn get-multi [ids]
  (map <-datomic
       (pull-many (dc/db conn) default-read ids )))



(defn find-conflicting [{:keys [:invoice/invoice-number :invoice/vendor :invoice/client :db/id]}]
  
  (->> (dc/q
         {:find  [(list 'pull '?e default-read)]
          :in    ['$ '?invoice-number '?vendor '?client '?invoice-id]
          :where '[[?e :invoice/invoice-number ?invoice-number]
                   [?e :invoice/vendor ?vendor]
                   [?e :invoice/client ?client]
                   (not [?e :invoice/status :invoice-status/voided])
                   [(not= ?e ?invoice-id)]]}
         (dc/db conn) invoice-number vendor client (or id 0))
       (map first)
       (map <-datomic)))


(defn get-existing-set []
  (let [vendored-results (set (dc/q {:find  ['?vendor '?client '?invoice-number]
                                     :in    ['$]
                                     :where '[[?e :invoice/invoice-number ?invoice-number]
                                              [?e :invoice/vendor ?vendor]
                                              [?e :invoice/client ?client]
                                              (not [?e :invoice/status :invoice-status/voided])
                                              ]}
                                    (dc/db conn)))
        vendorless-results (->> (dc/q {:find  ['?client '?invoice-number]
                                       :in    ['$]
                                       :where '[[?e :invoice/invoice-number ?invoice-number]
                                                (not [?e :invoice/vendor])
                                                [?e :invoice/client ?client]
                                                (not [?e :invoice/status :invoice-status/voided])
                                                ]}
                                      (dc/db conn))
                                (mapv (fn [[client invoice-number]]
                                        [nil client invoice-number]) )
                                set)]
    (into vendored-results vendorless-results)))
  
(defn filter-ids [ids]
  (if ids 
    (->> 
      (dc/q {:find  ['?e]
             :in    ['$ '[?e ...]]
             :where ['[?e :invoice/date]]}
            (dc/db conn) ids)
      (map first)
      vec)
    []))

(defn code-invoice
  ([invoice]
   (code-invoice invoice nil))
  ([invoice override-account-id]
   (mu/log ::trying-to-code-invoice
           :invoice invoice)
   (let [db                     (dc/db auto-ap.datomic/conn)
         client-id              (or (:db/id (:invoice/client invoice))
                                    (:invoice/client invoice))
         vendor-id              (or (:db/id (:invoice/vendor invoice))
                                    (:invoice/vendor invoice))
         date                   (:invoice/date invoice)
         vendor                 (dc/pull db '[*] vendor-id)
         due                    (when (:vendor/terms vendor)
                                  (-> date
                                      (coerce/to-date-time)
                                      (time/plus (time/days (d-vendors/terms-for-client-id vendor client-id)))
                                      coerce/to-date))
         automatically-paid?    (boolean (seq (map first (dc/q '[:find ?c
                                                                 :in $ ?v ?c
                                                                 :where [?v :vendor/automatically-paid-when-due ?c]]
                                                               db
                                                               vendor-id
                                                               client-id))))
         [schedule-payment-dom] (map first (dc/q '[:find ?dom
                                                   :in $ ?v ?c
                                                   :where [?v :vendor/schedule-payment-dom ?sp ]
                                                   [?sp :vendor-schedule-payment-dom/client ?c]
                                                   [?sp :vendor-schedule-payment-dom/dom ?dom]]
                                                 db
                                                 vendor-id
                                                 client-id))

         scheduled-payment (cond automatically-paid?
                                 due

                                 schedule-payment-dom
                                 (-> date
                                     coerce/to-date-time
                                     (next-dom schedule-payment-dom)
                                     coerce/to-date)
                                 :else nil)
         default-expense-account #:invoice-expense-account {:db/id    (random-tempid)
                                                            :account  (or override-account-id (d-vendors/account-for-client-id vendor client-id))
                                                            :location (:invoice/location invoice)
                                                            :amount   (:invoice/total invoice)}]
     (cond-> invoice
       true              (assoc :invoice/expense-accounts [default-expense-account])
       due               (assoc :invoice/due due)
       scheduled-payment (assoc :invoice/scheduled-payment scheduled-payment)))))


