(ns auto-ap.routes.exports
  (:require [auto-ap.datomic :refer [conn]]
            [auto-ap.datomic.clients :as d-clients]
            [auto-ap.datomic.ledger :as d-ledger]
            [auto-ap.datomic.transactions :as d-transactions]
            [auto-ap.datomic.vendors :as d-vendors]
            [buddy.sign.jwt :as jwt]
            [auto-ap.graphql :as graphql]
            [auto-ap.graphql.utils :refer [->graphql <-graphql assert-admin assert-can-see-client]]
            [auto-ap.routes.utils :refer [wrap-secure]]
            [clojure.tools.logging :as log]
            [auto-ap.logging :refer [error-event info-event warn-event]]
            [clj-time.coerce :as coerce :refer [to-date]]
            [clj-time.core :as time]
            [clojure.data.csv :as csv]
            [config.core :refer [env]]
            [compojure.core :refer [context defroutes GET wrap-routes routes]]
            [datomic.api :as d]
            [ring.middleware.json :refer [wrap-json-response]]
            [venia.core :as venia]
            [yang.time :refer [time-it]]))
(defn wrap-csv-response [handler]
  (fn [request]
    (let [response (handler request)]
      (println response)
      (update response :body #(with-open [w (java.io.StringWriter.)]
                              (csv/write-csv w %)
                              (.toString w))))))

(def api-key-authed-routes
  (context "/" []
            (GET "/sales/aggregated/export" {:keys [query-params identity :as params]}
                 (let [client-id (Long/parseLong (get query-params "client-id"))
                       identity (jwt/unsign (get query-params "key") (:jwt-secret env) {:alg :hs512})]
                   (assert-can-see-client  identity client-id)
                   (into (list)
                         (d/query {:query {:find '[?d4 (sum ?total) (sum ?tax) (sum ?tip)  (sum ?service-charge)]
                                           :in '[$ ?c]
                                           :where  '[[?s :sales-order/client ?c]
                                                     [?s :sales-order/date ?d]
                                                     [?s :sales-order/total ?total]
                                                     [?s :sales-order/tax ?tax]
                                                     [?s :sales-order/tip ?tip]
                                                     [?s :sales-order/service-charge ?service-charge]
                                                     [(clj-time.coerce/to-date-time ?d) ?d2]
                                                     [(auto-ap.time/localize ?d2) ?d3]
                                                     [(auto-ap.time/unparse ?d3 auto-ap.time/normal-date) ?d4]]}
                                   :args [(d/db conn) client-id]}))))))

(def admin-only-routes
  (context "/" []
           (GET "/invoices/export" {:keys [query-params identity] :as request}
                (assert-admin identity)
                (let [query [[:all_invoices
                              {:client-code (query-params "client-code")
                               :original-id (query-params "original")} 
                              
                              [:id :total :outstanding-balance :invoice-number :date :status :original-id
                               [:payments [:amount [:payment [:check-number :memo [:bank_account [:id :name :number :bank-name :bank-code :code]]]]]]
                               [:vendor [:name :id [:primary_contact [:name]] [:address [:street1 :city :state :zip]]]]
                               [:expense_accounts [:amount :id :location
                                                   [:account [:id :numeric-code :name]]]]
                               [:client [:name :id :code :locations]]]]]
                      invoices (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
                  
                  (list (:all-invoices (:data invoices)))))
           (GET "/payments/export" {:keys [query-params identity]}
                (assert-admin identity)
                (let [query [[:all_payments
                              {:client-code (query-params "client-code")
                               :original-id (query-params "original")} 
                              [:id :check-number :amount :memo :date :status :type :original-id
                               [:invoices [[:invoice [:id :original-id]] :amount]]
                               [:bank-account [:number :code :bank-name :bank-code :id]]
                               [:vendor [:name :id [:primary-contact [:name :email :phone]] [:default-account [:name :numeric-code :id]] [:address [:street1 :city :state :zip]]]]
                               [:client [:id :name :code]]
                               ]]]
                      payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
                  (list (:all-payments (:data payments)))))

           (GET "/sales/export" {:keys [query-params identity :as params]}
                (assert-admin identity)
                (println params)
                (let [query [[:all_sales_orders
                              (cond-> {:client-code (query-params "client-code")}
                                (query-params "after") (assoc :date-range {:start (query-params "after")
                                                                           :end nil})) 
                              [:id
                               :location 
                               :external_id
                               :total
                               :tip 
                               :tax 
                               :discount
                               :returns
                               :service_charge
                               :date 
                               [:charges [:type_name :total :tip]] 
                               [:line_items [:item_name :total :tax :discount :category]] 
                               [:client [:id :name :code]]]]]
                      payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))
                      parsedouble #(some-> % Double/parseDouble)  ]
                  (seq (map
                        (fn [s]
                          (-> s
                              (assoc :utc_date (:date s))
                              (update :date (fn [d]
                                              (coerce/to-string (coerce/to-local-date-time (time/to-time-zone (coerce/to-date-time d) (time/time-zone-for-id "America/Los_Angeles"))))))
                              (update :total parsedouble)
                              (update :tax parsedouble)
                              (update :discount parsedouble)
                              (update :tip parsedouble)
                              (update :line-items (fn [lis]
                                                    (map
                                                     (fn [li]
                                                       (-> li
                                                           (update :tax parsedouble)
                                                           (update :discount parsedouble)
                                                           (update :total parsedouble)))
                                                     lis)))
                              (update :charges (fn [charges]
                                                 (map
                                                  (fn [charge]
                                                    (-> charge
                                                        (update :tip parsedouble)
                                                        (update :total parsedouble)))
                                                  charges)))))
                        (:all-sales-orders (:data payments))))))

           

           (GET "/expected-deposit/export" {:keys [query-params identity]}
                (assert-admin identity)
                (let [query [[:all_expected_deposits
                              (cond-> {:client-code (query-params "client-code")}
                                (query-params "after") (assoc :date-range {:start (query-params "after")
                                                                           :end nil})) 
                              [:id
                               [:client [:id :name :code]]
                               :location 
                               :external_id
                               :total
                               :fee 
                               :date]]]
                      payments (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]
                  (seq (map
                        (fn [d]
                          (-> d
                              (update  :fee #(some-> % Double/parseDouble))
                              (update  :total #(some-> % Double/parseDouble))))
                        (:all-expected-deposits (:data payments))))))

           (GET "/clients/export" {:keys [query-params identity]}
                (assert-admin identity)
                (map <-graphql (d-clients/get-all)))

           (GET "/vendors/export" {:keys [query-params identity]}
                (assert-admin identity)
                (map <-graphql (d-vendors/get-graphql {})))
           (GET "/ledger/export" {:keys [query-params identity]}
                (log/info "exporting for " (query-params "client-code"))
                (assert-admin identity)
                (let [[result time] (time-it (transduce (comp
                                                         (map #(update % :journal-entry/date to-date))
                                                         (map <-graphql))
                                                        conj
                                                        (list)
                                                        (first (d-ledger/get-graphql {:count Integer/MAX_VALUE
                                                                                      :client-code (query-params "client-code")}))))]
                  (info-event "Export completed"
                              {:time (:time time)
                               :errors (seq (:errors result))})
                  result))

           (GET "/accounts/export" {:keys [query-params identity]}
                (assert-admin identity)
                
                (let [client-id (d-clients/code->id (query-params "client-code"))
                      query [[:accounts
                              [:id :numeric_code :type :applicability :location :name [:client_overrides [:name [:client [:id :code :name]]]]]]]
                      all-accounts (graphql/query identity (venia/graphql-query {:venia/queries (->graphql query)}))]

                  (list (transduce
                         (comp
                          (filter (fn [a]
                                    (let [overriden-clients (set (map (comp :id :client) (:client-overrides a)))]
                                      (or (= :global (:applicability a))
                                          (overriden-clients (str client-id))))))
                          (map (fn [a]
                                 (let [client->name (reduce
                                                     (fn [override co]
                                                       (assoc override (str (:id (:client co))) (:name co)))
                                                     {}
                                                     (:client-overrides a))]
                                   (-> a
                                       (assoc :global-name (:name a))
                                       (assoc :client-name (client->name (str client-id) (:name a)))
                                       (dissoc :client-overrides))))))
                         conj
                         []
                         (:accounts (:data all-accounts))))))

           (GET "/transactions/export" {:keys [query-params identity]}
                (assert-admin identity)
                (let [[transactions] (d-transactions/get-graphql {:client-code (query-params "client-code")
                                                                  #_#_:original-id (Integer/parseInt (query-params "original")) 
                                                                  :count Integer/MAX_VALUE})]
                  
                  
                  (map (comp ->graphql (fn [i]
                                         (cond-> i
                                           true (update  :transaction/date to-date)
                                           true (update  :transaction/post-date to-date)
                                           (:transaction/payment i) (update-in [:transaction/payment :payment/date] to-date)
                                           (:transaction/expected-deposit i) (update-in [:transaction/expected-deposit :expected-deposit/date] to-date))))
                       transactions)))

           (GET "/transactions/export2" {:keys [query-params identity]}
                (assert-admin identity)
                (let [db (d/db conn)]
                  (->> 
                   (d/query {:query {:find ['?e]
                                     :in ['$ '?client-code]
                                     :where ['[?e :transaction/client ?client-code]]}
                             :args [db [:client/code (query-params "client-code")]]})
                   (map first)
                   (map (fn [e]
                          (let [e (d/entity db e)
                                client (:transaction/client e)
                                bank-account (:transaction/bank-account e)]
                            {:id (:db/id e)
                             :date (:transaction/date e)
                             :post_date (:transaction/post-date e)
                             :client { :code (:client/code client)
                                      :id (:db/id client)
                                      :name (:client/name client)}
                             :amount (:transaction/amount e)
                             :description_original (:transaction/description-original e)
                             :approval_status (:transaction/approval-status e)
                             :bank_account {:name (:bank-account/name bank-account)
                                            :code (:bank-account/code bank-account)
                                            :id (:db/id bank-account)}}))))))

           (GET "/raw" {:keys [query-params identity]}
                (assert-admin identity)
                (log/info "Executing raw query " (get query-params "query" ))
                (into (list) (apply d/q (clojure.edn/read-string (get query-params "query" )) (into [(d/db conn)] (clojure.edn/read-string (get query-params "args" "[]"))))))))
(defroutes export-routes
  (routes 
   (wrap-routes api-key-authed-routes
                wrap-csv-response)

   (wrap-routes (wrap-routes admin-only-routes wrap-secure)
                wrap-json-response)))
