(ns auto-ap.graphql
  (:require
    [com.walmartlabs.lacinia.util :refer [attach-resolvers]]
    [com.walmartlabs.lacinia.schema :as schema]
    [com.walmartlabs.lacinia :refer [execute]]
    [com.walmartlabs.lacinia.executor :as executor]
    [com.walmartlabs.lacinia.resolve :as resolve]
    [auto-ap.db.invoices :as invoices]
    [auto-ap.utils :refer [by]]
    [auto-ap.db.vendors :as vendors]
    [auto-ap.db.companies :as companies]
    [auto-ap.db.users :as users]
    [auto-ap.db.checks :as checks]
    [auto-ap.routes.checks :as rchecks]
    [auto-ap.graphql.users :as gq-users]
    [auto-ap.db.reminders :as reminders]
    [auto-ap.db.invoices-checks :as invoices-checks]
    [auto-ap.db.utils :as utils]
    [clojure.walk :as walk]
    [clojure.string :as str])
  (:import
    (clojure.lang IPersistentMap)))


(def integreat-schema
  {
   :objects
   {
    :company
    {:fields {:id {:type 'Int}
              :name {:type 'String}
              :email {:type 'String}
              :bank_accounts {:type '(list :bank_account)}}}

    :bank_account
    {:fields {:id {:type 'Int}
              :number {:type 'String}
              :check_number {:type 'Int}
              :name {:type 'String}
              :bank_code {:type 'String}
              :bank_name {:type 'String}}}
    :vendor
    {:fields {:id {:type 'Int}
              :name {:type 'String}
              :invoice_reminder_schedule {:type 'String}}}
    :reminder
    {:fields {:id {:type 'Int}
              :email {:type 'String}
              :subject {:type 'String}
              :body {:type 'String}
              :scheduled {:type 'String}
              :sent {:type 'String}
              :vendor {:type :vendor
                       :resolve :get-vendor-for-invoice}}
     }

    :check {:fields {:id {:type 'Int}
                     :amount {:type 'String}
                     :s3_url {:type 'String}
                     :check_number {:type 'Int}}}

    :invoice_check
    {:fields {:id {:type 'Int}
              :amount {:type 'String}
              :check_id {:type 'Int}
              :check {:type :check
                      :resolve :get-check-by-id}}}

    :user
    {:fields {:id {:type 'Int}
              :name {:type 'String}
              :role {:type 'String}
              :companies {:type '(list :company)
                          :resolve :get-user-companies}}}

    :invoice
    {:fields {:id {:type 'Int}
              :total {:type 'String}
              :outstanding_balance {:type 'String}
              :invoice_number {:type 'String}
              :date {:type 'String}
              :company_id {:type 'Int}
              :checks {:type '(list :invoice_check)
                       :resolve :get-invoices-checks}
              :vendor {:type :vendor

                       :resolve :get-vendor-for-invoice}
              :company {:type :company
                        :resolve :get-company-for-invoice}}}


    :invoice_page {:fields {:invoices {:type '(list :invoice)}
                            :count {:type 'Int}
                            :total {:type 'Int}
                            :start {:type 'Int}
                            :end {:type 'Int}}}

    :reminder_page {:fields {:reminders {:type '(list :reminder)}
                             :count {:type 'Int}
                             :total {:type 'Int}
                             :start {:type 'Int}
                             :end {:type 'Int}}}
    :check_result {:fields {:invoices {:type '(list :invoice)}
                            :pdf_url {:type 'String}}}

    
    }

   

   :queries
   {:invoice_page {:type '(list :invoice_page)
                   :args {:imported {:type 'Boolean}
                          :status {:type 'String}
                          :company_id {:type 'Int}
                          :start {:type 'Int}
                          :sort_by {:type 'String}
                          :asc {:type 'Boolean}}
                   
                   :resolve :get-invoice-page}
    :reminder_page {:type '(list :reminder_page)
                    :args {:start {:type 'Int}
                           :sort_by {:type 'String}
                           :asc {:type 'Boolean}}
                    
                    :resolve :get-reminder-page}
    :company {:type '(list :company)
              :resolve :get-company}
    :vendor {:type '(list :vendor)
             :resolve :get-vendor}
    :user {:type '(list :user)
           :resolve :get-user}}

   :input-objects
   {
    :invoice_payment {:fields {:invoice_id {:type 'Int}
                               :amount {:type 'Float}}}

    :edit_user
    {:fields {:id {:type 'Int}
              :name {:type 'String}
              :role {:type 'String}
              :companies {:type '(list Int)}}}
    }

   :mutations
   {:print_checks {:type :check_result
                   :args {:invoice_payments  {:type '(list :invoice_payment)}
                          :bank_account_id {:type 'Int}
                          :company_id {:type 'Int}}
                   :resolve :mutation/print-checks}
    :edit_user {:type :user
                :args {:edit_user {:type :edit_user}}
                :resolve :mutation/edit-user}}})



(defn snake->kebab [s]
  (str/replace s #"_" "-"))

(defn kebab [x]
  (keyword (snake->kebab (name x))))

(defn kebab->snake [s]
  (str/replace s #"-" "_"))

(defn snake [x]
  (keyword (kebab->snake (name x))))

(defn ->graphql [m]
  (walk/postwalk
    (fn [node]
      (cond

        (keyword? node)
        (snake node)

        :else
        node))
    m))

(defn <-graphql [m]
  (walk/postwalk
    (fn [node]
      (cond

        (keyword? node)
        (kebab node)

        :else
        node))
    m))

(defn get-invoice-page [context args value]
  (let [extra-context
        (cond->  {}
          (executor/selects-field? context :invoice/vendor) (assoc :vendor-cache (by  :id (vendors/get-all)))
          (executor/selects-field? context :invoice/company) (assoc :company-cache (by  :id (companies/get-all))))

        invoices (map
                  ->graphql
                  (invoices/get-graphql (<-graphql args)))
        invoice-count (invoices/count-graphql (<-graphql args))]
    (resolve/with-context
      [{:invoices invoices
        :total invoice-count
        :count (count invoices)
        :start (:start args 0)
        :end (+ (:start args 0) (count invoices))}] extra-context)))

(defn get-reminder-page [context args value]
  (let [extra-context
        (cond->  {}
          (executor/selects-field? context :reminder/vendor) (assoc :vendor-cache (by  :id (vendors/get-all))))

        reminders (map
                  ->graphql
                  (reminders/get-graphql (<-graphql args)))
        reminder-count (reminders/count-graphql (<-graphql args))]
    (resolve/with-context
      [{:reminders reminders
        :total reminder-count
        :count (count reminders)
        :start (:start args 0)
        :end (+ (:start args 0) (count reminders))}] extra-context)))

(defn get-vendor-for-invoice [context args value]
  (->graphql
    (if-let [vendor-cache (:vendor-cache context)]
      (vendor-cache (:vendor_id value))
      (vendors/get-by-id (:vendor_id value)))))

(defn get-check-by-id [context args value]
  (->graphql
   (checks/get-by-id (:check_id value))))

(defn get-invoices-checks [context args value]
  (->graphql
   (invoices-checks/get-for-invoice-id (:id value))))

(defn get-company-for-invoice [context args value]
  (->graphql
    (if-let [company-cache (:company-cache context)]
      (company-cache (:company_id value))
      (companies/get-by-id (:company_id value)))))

(defn get-user-companies [context args value]
  (->graphql
    (if-let [company-cache (:company-cache context)]
      (map company-cache (:companies value))
      (map companies/get-by-id (:companies value)))))

(defn get-company [context args value]
  (->graphql
   (companies/get-all)))

(defn join-companies [users]
  (let [companies (by :id (companies/get-all))]
    (map
     (fn [u]
       (update u :companies #(map companies %)))
     users)))

(defn get-user [context args value]
  (let [users (users/get-all)
        
        extra-context (cond-> context 
                        (executor/selects-field? context :user/companies) (assoc :company-cache (by :id (companies/get-all))))]
    (resolve/with-context
      (->graphql users) extra-context)))

(defn get-vendor [context args value]
  (->graphql
   (vendors/get-all)))

(defn print-checks [context args value]
  (->graphql
   (rchecks/print-checks (map (fn [i] {:invoice-id (:invoice_id i)
                                      :amount (:amount i)})
                              (:invoice_payments args))
                         (:company_id args)
                         (:bank_account_id args))))

(defn edit-user [context args value]
  (Thread/sleep 1000)
  (->graphql
   (gq-users/edit-user (:edit_user args))))

(def schema
  (-> integreat-schema
      (attach-resolvers {:get-invoice-page get-invoice-page
                         :get-reminder-page get-reminder-page
                         :get-vendor-for-invoice get-vendor-for-invoice
                         :get-company-for-invoice get-company-for-invoice
                         :get-invoices-checks get-invoices-checks
                         :get-check-by-id get-check-by-id
                         :get-company get-company
                         :get-user get-user
                         :get-user-companies get-user-companies
                         :mutation/print-checks print-checks
                         :mutation/edit-user edit-user
                         :get-vendor get-vendor})
      schema/compile))



(defn simplify
  "Converts all ordered maps nested within the map into standard hash maps, and
   sequences into vectors, which makes for easier constants in the tests, and eliminates ordering problems."
  [m]
  (walk/postwalk
    (fn [node]
      (cond
        (instance? IPersistentMap node)
        (into {} node)

        (seq? node)
        (vec node)

        (keyword? node)
        (kebab node)

        :else
        node))
    m))
                 
(defn query
  ([q]
   (simplify (execute schema q nil nil)))
  ([q v]
   (simplify (execute schema q v nil))))
