(ns auto-ap.graphql.invoices
  (:require [auto-ap.graphql.utils :refer [->graphql <-graphql assert-can-see-client assert-admin]]
            
            [auto-ap.datomic.vendors :as d-vendors]
            [auto-ap.datomic.invoices :as d-invoices]
            [auto-ap.expense-accounts :as expense-accounts]
            [auto-ap.time :refer [parse iso-date]]
            [datomic.api :as d]
            [auto-ap.datomic :refer [uri]]
            [clj-time.coerce :as coerce]
            [clojure.set :as set]))

(defn get-invoice-page [context args value]
  (let [args (assoc args :id (:id context))
        invoices (map
                  ->graphql
                  (d-invoices/get-graphql (<-graphql (assoc args :id (:id context)))))
        invoice-count (d-invoices/count-graphql (<-graphql args))]
    [{:invoices invoices
      :total invoice-count
      :count (count invoices)
      :start (:start args 0)
      :end (+ (:start args 0) (count invoices))}]))

(defn get-all-invoices [context args value]
  (assert-admin (:id context))
  (map
   ->graphql
   (d-invoices/get-graphql (assoc (<-graphql args)
                                  :count Integer/MAX_VALUE))))

(defn reject-invoices [context {:keys [invoices] :as in} value]
  (assert-admin (:id context))
  
  (let [transactions (map (fn [i] [:db/retractEntity i ]) invoices)
        transaction-result @(d/transact (d/connect uri) transactions)]
    invoices))

(defn approve-invoices [context {:keys [invoices] :as in} value]
  (assert-admin (:id context))
  (let [transactions (map (fn [i] {:db/id i :invoice/import-status :import-status/imported}) invoices)
        transaction-result @(d/transact (d/connect uri) transactions)]
    invoices))

(defn add-invoice [context {{:keys [total invoice_number location client_id vendor_id vendor_name date] :as in} :invoice} value]
  (when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice_number
                                           :invoice/vendor vendor_id
                                           :invoice/client client_id}))
    (throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number})))
  (let [_ (assert-can-see-client (:id context) client_id)
        vendor (d-vendors/get-by-id vendor_id)
        
        expense-account-id (:vendor/default-expense-account vendor)
        _ (when-not expense-account-id
            (throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id} )))
        transaction [{:db/id "invoice"
                      :invoice/invoice-number invoice_number
                      :invoice/client client_id
                      :invoice/vendor vendor_id
                      :invoice/import-status :import-status/imported
                      :invoice/total total
                      :invoice/outstanding-balance total
                      :invoice/status :invoice-status/unpaid
                      :invoice/date  (coerce/to-date date)
                      :invoice/expense-accounts [{:invoice-expense-account/expense-account-id expense-account-id
                                                  :invoice-expense-account/location (get-in expense-accounts/expense-accounts [expense-account-id :location] location)
                                                  :invoice-expense-account/amount total}]}]
        transaction-result @(d/transact (d/connect uri) transaction)
        ]
    
    
    (-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
        (->graphql))))


(defn edit-invoice [context {{:keys [id invoice_number total vendor_id date client_id] :as in} :invoice} value]
  
  
  (let [invoice (d-invoices/get-by-id id)
        
        _ (when (seq (doto (d-invoices/find-conflicting {:db/id id
                                                         :invoice/invoice-number invoice_number
                                                         :invoice/vendor (:db/id (:invoice/vendor invoice))
                                                         :invoice/client (:db/id (:invoice/client invoice))})
                       println))
            (throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number})))
        paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
        _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
        updated-invoice (d-invoices/update {:db/id id
                                            :invoice/invoice-number invoice_number
                                            :invoice/date (coerce/to-date (parse date iso-date))
                                            :invoice/total total 
                                            :invoice/outstanding-balance (- total paid-amount)})]
    (-> updated-invoice
        (->graphql))))

(defn void-invoice [context {id :invoice_id} value]
 (let [invoice (d-invoices/get-by-id id)
       _ (println invoice)
  _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
  updated-invoice (d-invoices/update {:db/id id
    :invoice/total 0.0
    :invoice/outstanding-balance 0.0
                                      :invoice/status :invoice-status/voided
                                      :invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea)
                                                                               :invoice-expense-account/amount 0.0})
                                                                     (:invoice/expense-accounts invoice))})]

  (-> updated-invoice
   (->graphql))))


(defn unvoid-invoice [context {id :invoice_id} value]
  (let [invoice (d-invoices/get-by-id id)
        _ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
        conn (d/connect uri)
        history (d/history (d/db conn))
        txs (d/query {:query {:find ['?tx '?e '?original-status '?original-outstanding '?total '?ea '?ea-amount]
                              :where ['[?e :invoice/status :invoice-status/voided ?tx true]
                                      '[?e :invoice/status ?original-status ?tx false]
                                      '[?e :invoice/outstanding-balance ?original-outstanding ?tx false]
                                      '[?e :invoice/total ?total ?tx false]
                                      '[?ea :invoice-expense-account/amount ?ea-amount ?tx false]]
                              :in ['$ '?e]}
                      :args [history id]})
        [last-transaction] (->> txs (sort-by first) (last))]
    @(d/transact conn [(->> txs
                            (filter (fn [[tx]] (= tx last-transaction)))
                            (reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]]
                                      (-> new-transaction 
                                          (assoc :db/id entity
                                                 :invoice/total total
                                                 :invoice/status original-status
                                                 :invoice/outstanding-balance original-outstanding)

                                          (update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount}))
                                      ) {}))])

    (-> (d-invoices/get-by-id id)
        (->graphql))))




(defn edit-expense-accounts [context args value]
  ;; TODO - Can expense account id be used as a unique field? It may compose with component, meaning
  ;; that you don't have to figure out which ones to delete and which ones to add. Just set to 0.
  (assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
  (let [current-expense-accounts (:invoice/expense-accounts (d-invoices/get-by-id (:invoice_id args)))
        invoice-id (:invoice_id args)

        specified-ids (->> (:expense_accounts args)
                           (map :id)
                           set)
        
        existing-ids (->> current-expense-accounts
                          (map :db/id)
                          set)
        
        deleted (set/difference existing-ids specified-ids)
        updated (map
                 (fn [{:keys [id expense_account_id amount location]}]
                   {:db/id invoice-id 
                    :invoice/expense-accounts [#:invoice-expense-account {
                                                                         :amount (Double/parseDouble amount)
                                                                          :db/id id
                                                                         :expense-account-id expense_account_id
                                                                          :location location}
                                               ]}
                   )
                 (:expense_accounts args))]

    
    @(d/transact (d/connect uri) (concat updated
                                         (map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d])deleted)))
    
    
    (->graphql
     (d-invoices/get-by-id (:invoice_id args)))))

