(ns auto-ap.db.invoices
  (:require [auto-ap.db.utils :refer [clj->db kebab->snake db->clj get-conn query] :as utils]
            [auto-ap.parse :as parse]
            [auto-ap.db.companies :as companies]
            [auto-ap.db.invoices-checks :as invoices-checks]
            [auto-ap.db.vendors :as vendors]
            [auto-ap.entities.companies :as company]
            [auto-ap.entities.vendors :as vendor]
            [clojure.java.jdbc :as j]
            [clojure.string :as str]
            [honeysql.core :as sql]
            [honeysql.helpers :as helpers]))

(def all-keys #{:company-id :vendor-id :imported :potential-duplicate :total :invoice-number :date :outstanding-balance})

(defn insert-multi! [rows]
  (j/insert-multi! (get-conn)
                   :invoices
                   (map clj->db rows)))


(defn upsert-multi! [rows]
  (let [k (vec (map #(keyword (kebab->snake (name %))) [:company-id :vendor-id :invoice-number :total :date :imported :status :outstanding-balance]))
        column-names (str/join "," (map name k))]
    (reduce
     (fn [affected rows]
       (+ affected
          (first (j/db-do-prepared (get-conn)
                                   (sql/format {:with [[[:v (str "(" column-names ")")]
                                                        (helpers/values (->> rows
                                                                             (map clj->db )
                                                                             (map (apply juxt k))))]]
                                                :insert-into [[:invoices k]
                                                              {:select [:v.company-id :v.vendor-id :v.invoice-number :v.total (sql/raw "cast(v.date as timestamp)") :v.imported :v.status :v.outstanding-balance]
                                                               :from [:v]
                                                               :left-join [[:invoices :exist]
                                                                           [:and
                                                                            [:= :exist.invoice-number :v.invoice-number]
                                                                            [:= :exist.company-id :v.company-id]
                                                                            [:or [:= :exist.vendor-id :v.vendor-id]
                                                                             [:and
                                                                              [:= :exist.vendor-id nil]
                                                                              [:= :v.vendor-id nil]]]]]
                                                               :where [:= :exist.id nil] }] })))))
     0
     (partition-all 2000 rows))))

(def base-query (sql/build :select :invoices.*
                           :from :invoices))


(defn get-all []
  (query base-query))

(defn get-multi [ids]
  (query (-> base-query
             (helpers/merge-where [:in :id ids]))))

(defn get-by-id [id]
  (first (query (-> base-query
                    (helpers/merge-where [:= :id id])))))

(defn approve []
  (j/update! (get-conn) :invoices {:imported true} [] ))

(defn reject []
  (j/delete! (get-conn) :invoices ["imported = false"]))

(defn get-unpaid [company]
  (query
   (if company
     (-> base-query
         (helpers/merge-where [:= :imported true])
         (helpers/merge-where [:= :company-id (if (int? company)
                                                company
                                                (Integer/parseInt company))]))
     (-> base-query
         (helpers/merge-where [:= :imported true])))))

(defn get-pending [company]
  (query
   (if company
     (-> base-query
         (helpers/merge-where [:= :imported false])
         (helpers/merge-where [:= :company-id (if (int? company)
                                                company
                                                (Integer/parseInt company))]))
     (-> base-query
         (helpers/merge-where [:= :imported false])))))

(defn add-sort-by [q sort-by asc]
  (let [sort-by-key (keyword sort-by)]
    (cond (all-keys  sort-by-key)
          (helpers/merge-order-by q [sort-by-key (when-not asc :desc)])

          (= :vendor sort-by-key)
          (-> q
              (helpers/merge-left-join [:vendors :v] [:= :v.id :invoices.vendor-id] )
              (helpers/merge-order-by [:v.name (when-not asc :desc)]))

          (= :company sort-by-key)
          (-> q
              (helpers/merge-left-join [:companies :c] [:= :c.id :invoices.company-id] )
              (helpers/merge-order-by [:c.name (when-not asc :desc)]))

          :else
          q)))



(defn base-graphql [{:keys [imported company-id status]}]
  (cond-> base-query
    (not (nil? imported)) (helpers/merge-where [:= :imported imported])
    (not (nil? status)) (helpers/merge-where [:= :status status])
    (not (nil? company-id)) (helpers/merge-where [:= :company-id company-id])))

(defn get-graphql [{:keys [start sort-by asc] :as args}]
  (query
   (cond-> (base-graphql args)
     (not (nil? sort-by) ) (add-sort-by sort-by asc)
     true (assoc :limit 20)
     start (assoc :offset start))))

(defn count-graphql [args]
  (:count (first (query
                  (assoc (base-graphql args) :select [:%count.*])))))

(defn import [parsed-invoices companies vendors]
  (insert-multi!
   (for [{:keys [total date invoice-number customer-identifier vendor-code] :as row} parsed-invoices]
     (do
       (dissoc (assoc row
                      :company-id (:id (parse/best-match companies customer-identifier))
                      :vendor-id (:id (first (filter #(= (:code %) vendor-code) vendors)))
                      :imported false
                      :potential-duplicate false)
               :vendor-code)))))

(defn apply-payment [invoice-id amount]
  (j/db-do-prepared (get-conn)
                    (-> (helpers/update :invoices)
                        (helpers/sset {:outstanding-balance (sql/call :- :outstanding-balance amount)})
                        (helpers/where [:= :id invoice-id])
                        (sql/format)))
  (j/db-do-prepared (get-conn)
                    (-> (helpers/update :invoices)
                        (helpers/sset {:status "paid"})
                        (helpers/where [:and [:< :outstanding-balance 0.01]
                                        [:= :id invoice-id]])
                        (sql/format))))
