(ns auto-ap.datomic.invoices
  (:require [datomic.api :as d]
            [auto-ap.datomic :refer [uri remove-nils merge-query apply-pagination apply-sort-3 add-sorter-fields]]
            [auto-ap.graphql.utils :refer [limited-clients]]
            [auto-ap.parse :as parse]
            [clj-time.coerce :as c]
            [clojure.set :refer [rename-keys]]
            [clojure.string :as str]
            [clojure.tools.logging :as log]))

(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 [*]}]}
                    {:invoice-payment/_invoice [* {:invoice-payment/payment [* {:payment/status [*]}
                                                                             {:payment/bank-account [*]}
                                                                             {:transaction/_payment [*]}]}]}])

(defn <-datomic [x]
  (-> x
       (update  :invoice/date c/from-date)
       (update  :invoice/due c/from-date)
       (update  :invoice/scheduled-payment c/from-date)
       (update  :invoice/status :db/ident)
       (rename-keys  {:invoice-payment/_invoice :invoice/payments})))

(defn raw-graphql-ids [db args]
  (->> (doto (cond-> {:query {:find []
                              :in ['$]
                              :where ['[?e :invoice/client]]}
                      :args [(d/db (d/connect uri))]}

               (:exact-match-id args)
               (merge-query {:query {:in ['?e]
                                     :where []}
                             :args [(:exact-match-id args)]})
               (limited-clients (:id args))
               (merge-query {:query {:in ['[?xx ...]]
                                     :where ['[?e :invoice/client ?xx]]}
                             :args [ (set (map :db/id (limited-clients (:id args))))]})
               (: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 (:date-range args)) (merge-query {:query {:in '[?start-date]
                                                                 :where ['[?e :invoice/date ?date]
                                                                         '[(>= ?date ?start-date)]]}
                                                         :args [(c/to-date (:start (:date-range args)))]})

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

               (:start (:due-range args)) (merge-query {:query {:in '[?start-due]
                                                                :where ['[?e :invoice/due ?due]
                                                                        '[(>= ?due ?start-due)]]}
                                                        :args [(c/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 [(c/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)]})

               (: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 ]
                                     :where ['[?e :invoice/client]
                                             '[?e :invoice/date ?sort-default]]}}) )
         (#(log/info %)))
       

       (d/query)
       (apply-sort-3 args)
       (apply-pagination args)))


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

(defn sum-outstanding [ids]
  
  (->> 
   (d/query {:query {:find ['?id '?o]
                     :in ['$ '[?id ...]]
                     :where ['[?id :invoice/outstanding-balance ?o]]
                     }
             :args [(d/db (d/connect uri))
                    ids]})
   (map last)
   (reduce
    +
    0.0)))

(defn get-graphql [args]
  
  (let [db (d/db (d/connect uri))
        {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)
        outstanding (sum-outstanding ids-to-retrieve)]

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

(defn get-by-id [id]
  (-> (d/db (d/connect uri))
      (d/pull  default-read id)
      (<-datomic)))

(defn update [update]
  @(d/transact (d/connect uri) [update])
  (get-by-id (:db/id update) ))

(defn get-multi [ids]
  (map <-datomic
       (-> (d/db (d/connect uri))
           (d/pull-many default-read ids))))



(defn find-conflicting [{:keys [:invoice/invoice-number :invoice/vendor :invoice/client :db/id]}]
  
  (->> (d/query
        (cond-> {:query {: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)]]}
                 
                 :args [(d/db (d/connect uri)) invoice-number vendor client (or id 0)]}))
       (map first)
       
       (map <-datomic)))


(defn get-existing-set []
  (let [vendored-results (set (d/query {:query {: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])
                                                         ]}
                                        
                                        :args [(d/db (d/connect uri))]}))
        vendorless-results (->> (d/query {:query {: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])
                                                           ]}
                                          
                                          :args [(d/db (d/connect uri))]})
                                (mapv (fn [[client invoice-number]]
                                        [nil client invoice-number]) )
                                set)]
    (into vendored-results vendorless-results)))
  

