(ns auto-ap.ezcater.core
  (:require
  [auto-ap.datomic :refer [conn random-tempid]]
   [datomic.api :as dc]
   [clj-http.client :as client]
   [venia.core :as v]
   [clojure.string :as str]
   [clojure.data.json :as json]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]
   [clojure.set :as set]
   [auto-ap.time :as atime]
   [auto-ap.logging :as alog]
   [cemerick.url :as url]))

(defn query [{:ezcater-integration/keys [api-key]} q]
  (-> (client/post "https://api.ezcater.com/graphql/"
                   {:headers {"Authorization" api-key
                              "Content-Type"  "application/json"}
                    :body    (json/write-str {"query" (v/graphql-query q)})
                    :as      :json})
      :body
      :data
      ))

(defn get-caterers [integration]
  (:caterers (query integration {:venia/queries [{:query/data
                                                  [:caterers [:name :uuid [:address [:name :street]]]]}]} )))

(defn get-subscriptions [integration]
  (->> (query integration {:venia/queries [{:query/data
                                            [:subscribers [:id [:subscriptions [:parentId :parentEntity :eventEntity :eventKey]] ]]}]} )
       :subscribers
       first
       :subscriptions))

(defn get-integrations []
  (map first (dc/q '[:find (pull ?i [:ezcater-integration/api-key
                                    :ezcater-integration/subscriber-uuid
                                    :db/id
                                    :ezcater-integration/integration-status [:db/id]])
                    :in $
                    :where [?i :ezcater-integration/api-key]]
                  (dc/db conn))))

(defn mark-integration-status [integration integration-status]
  @(dc/transact conn
               [{:db/id                                  (:db/id integration)
                 :ezcater-integration/integration-status (assoc integration-status
                                                                :db/id (or (-> integration :ezcater-integration/integration-status :db/id)
                                                                           (random-tempid)))}]))

(defn upsert-caterers
  ([integration]
   @(dc/transact
     conn
     (for [caterer (get-caterers integration)]
       {:db/id                        (:db/id integration) 
        :ezcater-integration/caterers [{:ezcater-caterer/name         (str (:name caterer) " (" (:street (:address caterer)) ")")
                                        :ezcater-caterer/search-terms (str (:name caterer) " " (:street (:address caterer)))
                                        :ezcater-caterer/uuid         (:uuid caterer)}]}))))

(defn upsert-used-subscriptions
  ([integration]
   (let [extant (get-subscriptions integration)
         to-ensure (set (map first (dc/q '[:find ?cu
                                          :in $
                                          :where [_ :client/ezcater-locations ?el]
                                          [?el :ezcater-location/caterer ?c]
                                          [?c :ezcater-caterer/uuid ?cu]]
                                        (dc/db conn))))
         to-create (set/difference
                     to-ensure
                     (set (map :parentId extant)))]
     (doseq [parentId to-create]
       (query integration
              {:venia/operation {:operation/type :mutation
                                 :operation/name "createSubscription"}
               :venia/queries   [[:createSubscription
                                  {:subscriptionParams {:subscriberId (:ezcater-integration/subscriber-uuid integration)
                                                        :parentEntity 'Caterer
                                                        :parentId     parentId
                                                        :eventEntity  'Order
                                                        :eventKey     'accepted}}
                                  [[:subscription [:parentId :parentEntity :eventEntity :eventKey]]]]]})
       (query integration
              {:venia/operation {:operation/type :mutation
                                 :operation/name "createSubscription"}
               :venia/queries   [[:createSubscription
                                  {:subscriptionParams {:subscriberId (:ezcater-integration/subscriber-uuid integration)
                                                        :parentEntity 'Caterer
                                                        :parentId     parentId
                                                        :eventEntity  'Order
                                                        :eventKey     'cancelled}}
                                  [[:subscription [:parentId :parentEntity :eventEntity :eventKey]]]]]})))))


#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn upsert-ezcater
  ([] (upsert-ezcater (get-integrations)))
  ([integrations]
   (doseq [integration integrations]
     (mark-integration-status integration {:integration-status/last-attempt (coerce/to-date (time/now))})
     (try
       (upsert-caterers integration)
       (upsert-used-subscriptions integration)
       (mark-integration-status integration {:integration-status/state        :integration-state/success
                                             :integration-status/last-updated (coerce/to-date (time/now))})

       (catch Exception e
         (alog/warn ::cant-upsert-ezcater
                    :exception e)
         (mark-integration-status integration {:integration-status/state   :integration-state/failed
                                               :integration-status/message (.getMessage e)}))))))

(defn get-caterer [caterer-uuid]
  (dc/pull (dc/db conn)
          '[:ezcater-caterer/name
            {:ezcater-integration/_caterers [:ezcater-integration/api-key]}
            {:ezcater-location/_caterer [:ezcater-location/location
                                         {:client/_ezcater-locations [:client/code]}]}]
          [:ezcater-caterer/uuid caterer-uuid]))


(defn round-carry-cents [f]
  (with-precision 2 (double (.setScale (bigdec f) 2 java.math.RoundingMode/HALF_UP))))

(defn commision [order]

  (let [commision% (cond
                     (= "CLUB_SODA" (:orderSourceType order))
                     0.25M

                     (= "MARKETPLACE" (:orderSourceType order))
                     0.15M
                     :else
                     0.07M)]
    (round-carry-cents 
      (* commision%
         0.01M
         (+
           (-> order :totals :subTotal :subunits )
           (reduce +
                   0
                   (map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order)))))))))

(defn ccp-fee [order]
  (round-carry-cents 
    (* 0.000299M
       (+
         (-> order :totals :subTotal :subunits )
         (-> order :totals :salesTax :subunits )
         (reduce +
                 0
                 (map (comp :subunits :cost) (:feesAndDiscounts (:catererCart order))))))))

(defn order->sales-order [{{:keys [timestamp]} :event {:keys [orderItems]} :catererCart :keys [client-code client-location uuid] :as order}]
  (let [adjustment (round-carry-cents (- (+ (-> order :totals :subTotal :subunits (* 0.01))
                                            (-> order :totals :salesTax :subunits (* 0.01)))
                                         (-> order :catererCart :totals :catererTotalDue )
                                         (commision order)
                                         (ccp-fee order)))
        service-charge (+ (commision order) (ccp-fee order))
        tax (-> order :totals :salesTax :subunits (* 0.01))
        tip (-> order :totals :tip :subunits (* 0.01))]
    #:sales-order
    {:date        (atime/localize (coerce/to-date-time timestamp))
     :external-id (str "ezcater/order/" client-code "-" client-location "-" uuid)
     :client      [:client/code client-code]
     :location    client-location
     :reference-link (str (url/url  "https://ezmanage.ezcater.com/orders/" uuid ))
     :line-items  [#:order-line-item
                   {:external-id (str "ezcater/order/" client-code "-" client-location "-" uuid "-" 0)
                    :item-name   "EZCater Catering"
                    :category    "EZCater Catering"
                    :discount    adjustment
                    :tax         tax
                    :total       (+ (-> order :totals :subTotal :subunits (* 0.01))
                                    tax
                                    tip)}]
     :charges     [#:charge
                   {:type-name   "CARD"
                    :date        (atime/localize (coerce/to-date-time timestamp))
                    :client      [:client/code client-code]
                    :location    client-location
                    :external-id (str "ezcater/charge/" uuid)
                    :processor   :ccp-processor/ezcater
                    :total       (+ (-> order :totals :subTotal :subunits (* 0.01))
                                    tax
                                    tip)
                    :tip         tip}]

     :total          (+ (-> order :totals :subTotal :subunits (* 0.01))
                        tax
                        tip)
     :discount       adjustment
     :service-charge service-charge
     :tax            tax
     :tip            tip
     :returns        0.0
     :vendor         :vendor/ccp-ezcater}))


(defn get-by-id [integration id]
  (query 
    integration
    {:venia/queries [[:order {:id id}
                      [:uuid
                       :orderNumber
                       :orderSourceType
                       [:caterer
                        [:name
                         :uuid
                         [:address [:street]]]]
                       [:event
                        [:timestamp
                         :catererHandoffFoodTime
                         :orderType]]
                       [:catererCart [[:orderItems 
                                       [:name
                                        :quantity
                                        :posItemId
                                        [:totalInSubunits
                                         [:currency
                                          :subunits]]]]
                                      [:totals 
                                       [:catererTotalDue]]
                                      [:feesAndDiscounts
                                       {:type 'DELIVERY_FEE}
                                       [[:cost
                                         [:currency
                                          :subunits]]]]]]
                       [:totals [[:customerTotalDue
                                  [
                                   :currency
                                   :subunits
                                   ]]
                                 [:pointOfSaleIntegrationFee
                                  [
                                   :currency
                                   :subunits
                                   ]]
                                 [:tip
                                  [:currency
                                   :subunits]]
                                 [:salesTax
                                  [
                                   :currency
                                   :subunits
                                   ]]
                                 [:salesTaxRemittance
                                  [:currency
                                   :subunits
                                   ]]
                                 [:subTotal
                                  [:currency
                                   :subunits]]]]]]]}))

(defn lookup-order [json]
  (let [caterer     (get-caterer (get json "parent_id"))
        integration (:ezcater-integration/_caterers caterer)
        client      (-> caterer :ezcater-location/_caterer first :client/_ezcater-locations :client/code)
        location    (-> caterer :ezcater-location/_caterer first :ezcater-location/location)]
    (if (and client location)
      (doto 
          (-> (get-by-id integration (get json "entity_id"))
              (:order)
              (assoc :client-code client
                     :client-location location))
        (#(alog/info ::order-details :detail %)))
      (alog/warn ::caterer-no-longer-has-location :json json))))

(defn import-order [json]
  ;; {"id" "bf3dcf5c-a68f-42d9-9084-049133e03d3d", "parent_type" "Caterer", "parent_id" "91541331-d7ae-4634-9e8b-ccbbcfb2ce70", "entity_type" "Order", "entity_id" "9ab05fee-a9c5-483b-a7f2-14debde4b7a8", "key" "accepted", "occurred_at" "2022-07-21T19:21:07.549Z"}
  (alog/info
    ::try-import-order
    :json json)
  @(dc/transact conn (filter identity
                            [(some-> json
                                     (lookup-order)
                                     (order->sales-order)
                                     (update :sales-order/date coerce/to-date)
                                     (update-in [:sales-order/charges 0 :charge/date] coerce/to-date))])))

(defn upsert-recent []
  (upsert-ezcater)
  (let [last-sunday      (coerce/to-date (time/plus (second (->> (time/today)
                                                                 (iterate #(time/plus % (time/days -1)))
                                                                 (filter #(= 7 (time/day-of-week %)))))
                                                    (time/days 1)))
        orders-to-update (doall (for [[order uuid] (dc/q '[:find ?eid ?uuid
                                                          :in $ ?start
                                                          :where [?e :sales-order/vendor :vendor/ccp-ezcater]
                                                          [?e :sales-order/date ?d]
                                                          [(>= ?d ?start)]
                                                          [?e :sales-order/external-id ?eid]
                                                          [?e :sales-order/client ?c]
                                                          [?c :client/ezcater-locations ?l]
                                                          [?l :ezcater-location/caterer ?c2]
                                                          [?c2 :ezcater-caterer/uuid ?uuid]]
                                                        (dc/db conn)
                                                        last-sunday)
                                      :let         [_ (alog/info ::considering
                                                                 :order order)
                                                    id (last (str/split order #"/"))
                                                    id (str/join "-" (drop 2 (str/split order #"-")))
                                                    lookup-map {"id" "bf3dcf5c-a68f-42d9-9084-049133e03d3d",
                                                                "parent_type" "Caterer",
                                                                "parent_id" uuid,
                                                                "entity_type" "Order",
                                                                "entity_id" id,
                                                                "key" "accepted",
                                                                "occurred_at" "2022-07-21T19:21:07.549Z"}
                                                    ezcater-order (lookup-order lookup-map)
                                                    extant-order (dc/pull (dc/db conn) '[:sales-order/total
                                                                                       :sales-order/tax
                                                                                       :sales-order/tip
                                                                                       :sales-order/discount
                                                                                       :sales-order/external-id
                                                                                       {:sales-order/charges [:charge/tax
                                                                                                              :charge/tip
                                                                                                              :charge/total
                                                                                                              :charge/external-id]
                                                                                        :sales-order/line-items [:order-line-item/external-id
                                                                                                                 :order-line-item/total
                                                                                                                 :order-line-item/tax
                                                                                                                 :order-line-item/discount]}]
                                                                         [:sales-order/external-id order])

                                                    updated-order (-> (order->sales-order ezcater-order)
                                                                      (select-keys
                                                                        #{:sales-order/total
                                                                          :sales-order/tax
                                                                          :sales-order/tip
                                                                          :sales-order/discount
                                                                          :sales-order/charges
                                                                          :sales-order/external-id
                                                                          :sales-order/line-items})
                                                                      (update :sales-order/line-items
                                                                              (fn [c]
                                                                                (map #(select-keys % #{:order-line-item/external-id
                                                                                                       :order-line-item/total
                                                                                                       :order-line-item/tax
                                                                                                       :order-line-item/discount}) c)))
                                                                      (update :sales-order/charges (fn [c]
                                                                                                     (map #(select-keys % #{:charge/tax :charge/tip :charge/total
                                                                                                                            :charge/external-id}) c))))]

                                      :when (not= updated-order extant-order)]

                                  updated-order))]
    (alog/info :found-orders-to-update
               :orders orders-to-update)
    @(dc/transact conn orders-to-update)))


