(ns auto-ap.ssr.transaction.insights
  (:require
   [auto-ap.client-routes :as client-routes]
   [auto-ap.datomic :refer [conn visible-clients]]
   [auto-ap.rule-matching :refer [spread-cents]]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.ssr.components :as com]
   [auto-ap.ssr.svg :as svg]
   [auto-ap.ssr.ui :refer [base-page]]
   [auto-ap.ssr.utils :refer [html-response modal-response]]
   [auto-ap.time :as atime]
   [bidi.bidi :as bidi]
   [cemerick.url :as url]
   [clj-http.client :as http]
   [clj-time.coerce :as coerce]
   [datomic.api :as dc]
   [hiccup2.core :as hiccup]
   [iol-ion.tx :refer [random-tempid]]
   [auto-ap.ssr.hx :as hx]))

(def pull-expr [:transaction/description-original
                :db/id

                :transaction/outcome-recommendation
                :transaction/amount
                {:transaction/client              [:client/code]
                 :transaction/bank-account        [:bank-account/code]}
                :transaction/account-confidence
                :transaction/date])
(defn parse-outcome [tx]
  (update tx :transaction/outcome-recommendation
          (fn [ors]
            (map
             (fn [[v a c s]]
               {:vendor (dc/pull (dc/db conn)
                                 [:vendor/name :db/id]
                                 v)
                :account (dc/pull (dc/db conn)
                                  [:account/name :db/id]
                                  a)
                :count c
                :seen-by-client? s})
             ors))))

(defn transaction-recommendations [identity clients & {:keys [after]}]
  (let [visible-clients (visible-clients identity)]
    (->>
     (dc/qseq {:query '[:find (pull ?t pull-expr)
                        :in $ ?starting [?c ...] pull-expr
                        :where
                        [?t :transaction/outcome-recommendation]
                        [?t :transaction/client ?c]
                        [?t :transaction/approval-status :transaction-approval-status/unapproved]
                        ;; [?t :transaction/vendor] ;; should be not
                        [?t :transaction/date ?d]
                        [(>= ?d ?starting)]]

               :args [(dc/db conn)
                      (iol-ion.query/recent-date 300)
                      (map :db/id clients)
                      
                      pull-expr]})
     (map first)
     (drop-while (fn [x]
                   (if after
                     (not= (Long/parseLong after) (:db/id x))
                     false)))
     (#(if after
         (drop 1 %)
         %))
     (map parse-outcome)
     (take 50)
     (into []))))


(defn get-pinecone [transaction-id]
  (-> 
    (http/get (-> "https://transactions-a8257ba.svc.us-west4-gcp-free.pinecone.io/vectors/fetch"
                  url/url
                  (assoc :query {:ids transaction-id})
                  str)
              {:headers {"Api-Key" "f2d3a78e-bcea-4fcd-88b6-2527b8423607"}
               :as      :json
               :keywordize? false})
    :body
    :vectors
    ((keyword (str transaction-id)))
    :values))

(defn get-pinecone-similarities [transaction-id]
  (filter 
    (fn [{:keys [score]}]
      (> score 0.95)
      )
    (-> 
      (http/post (-> "https://transactions-a8257ba.svc.us-west4-gcp-free.pinecone.io/query"
                     url/url
                     str)
                 {:headers      {"Api-Key" "f2d3a78e-bcea-4fcd-88b6-2527b8423607"}
                  :form-params  {"vector"          (get-pinecone transaction-id)
                                 "topK"            100,
                                 "includeMetadata" true
                                 "namespace"       ""}
                  :content-type :json
                  :as           :json})
      :body
      :matches)))

(defn pinecone-similarity-list [transaction-id]
  (for [{{:keys [amount date description vendor]} :metadata score :score id :id} (get-pinecone-similarities transaction-id)
        :let                                                                     [vendor-name (:vendor/name (:transaction/vendor (dc/pull (dc/db conn) [{:transaction/vendor [:vendor/name]} ] (Long/parseLong id))))
                                                                                  account-code (-> (dc/pull (dc/db conn) [{:transaction/accounts [{:transaction-account/account [:account/numeric-code]}]} ] (Long/parseLong id))
                                                                                                   :transaction/accounts
                                                                                                   first
                                                                                                   :transaction-account/account
                                                                                                   :account/numeric-code)]
        :when                                                                    (or vendor-name account-code)]
    {:vendor-name  vendor-name
     :numeric-code account-code
     :amount amount
     :date date
     :description description
     :score score}))


(defn transaction-row [r & {:keys [hide-actions? class last? other-params]}]
  (com/data-grid-row
   (cond-> {:class class}
     other-params (merge other-params)

     last? (assoc :hx-get (bidi/path-for ssr-routes/only-routes
                                         :transaction-insight-rows
                                         :after (:db/id r))
                  :hx-trigger "intersect once"
                  :hx-indicator "#insight-table"
                  :hx-swap "afterend"))
   (com/data-grid-cell {}  (:client/code (:transaction/client r)))
   (com/data-grid-cell {} (:bank-account/code (:transaction/bank-account r)))
   (com/data-grid-cell {} (some-> (:transaction/date r) coerce/to-date-time (atime/unparse-local  atime/normal-date)))

   (com/data-grid-cell {}  (str (:transaction/description-original r)))
   (com/data-grid-cell {}
                       (if (> (:transaction/amount r) 0.0)
                         [:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
                         [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))]))

   (com/data-grid-right-stack-cell {}
                                   [:div.flex.gap-2.flex-col {:style {:width "25em"}}
                                    (for [or (take 3 (sort-by (comp - :count)
                                                              (:transaction/outcome-recommendation r)))]
                                      [:form {:hx-post (bidi/path-for ssr-routes/only-routes
                                                                      :transaction-insight-code
                                                                      :transaction-id (:db/id r))
                                              :hx-target "closest tr"
                                              :hx-swap "outerHTML"
                                              :disabled hide-actions?}
                                       (when-let [vendor-id (:db/id (:vendor or))]
                                         [:input {:type :hidden :value vendor-id :name "vendor"}])
                                       (when-let [account-id (:db/id (:account or))]
                                         [:input {:type :hidden :value account-id :name "account"}])

                                       (com/button {:color (if (:seen-by-client? or)
                                                             :primary
                                                             :secondary)
                                                    :style {:position "relative"
                                                            :display "block"
                                                            :width "100%"}}
                                                   (:vendor/name (:vendor or))
                                                   (when (:vendor/name (:vendor or))
                                                     " | ")
                                                   (:account/name (:account or))
                                                   (com/badge {:color :secondary}
                                                              (:count or)))])
                                    [:div.flex.flex-row.gap-2
                                     (com/button {:hx-get    (bidi/path-for ssr-routes/only-routes
                                                                            :transaction-insight-explain
                                                                            :transaction-id (:db/id r))
                                                  :hx-target "#modal-holder"
                                                  :hx-swap   "outerHTML"}
                                                 [:div.flex
                                                  svg/question
                                                  "Explain"])
                                     (com/button {:hx-delete    (bidi/path-for ssr-routes/only-routes
                                                                               :transaction-insight-disapprove
                                                                               :transaction-id (:db/id r))
                                                  :hx-target "closest tr"
                                                  :hx-swap   "outerHTML"}
                                                 [:div.flex
                                                  svg/question
                                                  "Reject"])]])))

(defn code [{:keys [identity session]  {:keys [transaction-id]} :route-params {:strs [vendor account]} :form-params}]
  (let [approval-details (dc/pull (dc/db conn) [:transaction/recommended-vendor
                                                :transaction/amount
                                                :db/id
                                                {:transaction/client [:client/locations]}]
                                  (cond-> transaction-id
                                    string? (Long/parseLong)))
        account (dc/pull (dc/db conn) [:account/location :db/id]
                         (cond-> account
                           string? (Long/parseLong)))
        cents-to-distribute (int (Math/round (Math/abs (* (:transaction/amount approval-details) 100))))
        valid-locations  (or
                          (some-> approval-details :transaction/recommended-account :account/location vector)
                          (->> approval-details
                               :transaction/client
                               :client/locations))
        updated-transaction [:upsert-transaction {:db/id                (:db/id approval-details)
                                                  :transaction/approval-status :transaction-approval-status/approved
                                                  :transaction/vendor   (some-> vendor not-empty (Long/parseLong))
                                                  :transaction/accounts (->> valid-locations
                                                                             (map
                                                                              (fn [cents location]
                                                                                {:db/id                        (random-tempid)
                                                                                 :transaction-account/account  (-> account :db/id)
                                                                                 :transaction-account/amount   (* 0.01 cents)
                                                                                 :transaction-account/location location})
                                                                              (spread-cents cents-to-distribute (count valid-locations))))}]
        db-before (dc/db conn)]
    @(dc/transact conn [updated-transaction])
    (html-response (transaction-row
                    (parse-outcome (dc/pull db-before
                                    pull-expr
                                    (Long/parseLong transaction-id)))
                    :hide-actions? true
                    :class "live-added"
                    :other-params (hx/alpine-mount-then-disappear {})))))

(defn disapprove [{:keys [identity session]  {:keys [transaction-id]} :route-params}]
  (let [transaction-id (cond-> transaction-id string? (Long/parseLong))
        db-before (dc/db conn)]
    @(dc/transact conn [[:upsert-transaction {:db/id transaction-id :transaction/outcome-recommendation nil}]])
    (html-response (transaction-row
                    (parse-outcome (dc/pull db-before  pull-expr transaction-id))
                    :class "live-removed"
                    :hide-actions? true
                    :other-params (hx/alpine-mount-then-disappear {})))))

(defn explain [{:keys [identity session] {:keys [transaction-id]} :route-params}]
  (let [r       (dc/pull (dc/db conn)
                   pull-expr
                   (Long/parseLong transaction-id))
        similar (pinecone-similarity-list transaction-id)]
    (modal-response
      (com/modal {}
                 (com/modal-card {:style {:width "900px"}}
                                 [:div.flex [:div.p-2 "Similar Transactions"]]
                                 (com/data-grid {:headers [(com/data-grid-header {:name "Date"
                                                                                  :key  "date"})
                                                           (com/data-grid-header {:name "Description"
                                                                                  :key  "description"})
                                                           (com/data-grid-header {:name "Amount"
                                                                                  :key  "amount"})
                                                           (com/data-grid-header {:name "Vendor"
                                                                                  :key  "vendor"})
                                                           (com/data-grid-header {:name "Account"
                                                                                  :key  "account"})
                                                           (com/data-grid-header {:name "Score"
                                                                                  :key  "score"})]}

                                                (com/data-grid-row {:class "bg-primary-200"}
                                                                   (com/data-grid-cell {:class "text-left font-bold"} (some-> r :transaction/date coerce/to-date-time (atime/unparse-local  atime/normal-date)))
                                                                   (com/data-grid-cell {:class "text-left font-bold"} (-> r :transaction/description-original) )
                                                                   (com/data-grid-cell {:class "font-bold"} (if (> (-> r :transaction/amount) 0.0)
                                                                                            [:div.tag.is-success.is-light (str "$" (Math/round (:transaction/amount r)))]
                                                                                            [:div.tag.is-danger.is-light (str "$" (Math/round (:transaction/amount r)))]))
                                                                   (com/data-grid-cell {})
                                                                   (com/data-grid-cell {})
                                                                   (com/data-grid-cell {}))

                                                (com/data-grid-row {}
                                                                   (take 10
                                                                         (for [{:keys [amount date description vendor-name numeric-code score]} similar]
                                                                           (com/data-grid-row
                                                                             {}
                                                                             (com/data-grid-cell {:class "text-left"} (subs date 0 10))
                                                                             (com/data-grid-cell {:class "text-left"} description )
                                                                             (com/data-grid-cell {} (some->> amount double (format "$%.2f")))
                                                                             (com/data-grid-cell {} vendor-name)
                                                                             (com/data-grid-cell {} numeric-code)
                                                                             (com/data-grid-cell {} (format "%.1f%%" (* 100 (double score)))))))))
                                 [:div])))))

(defn transaction-rows* [{:keys [clients identity after]}]
  (let [recommendations (transaction-recommendations identity clients :after after)]
    (if (seq recommendations)
      (for [r    recommendations
            :let [last? (= r (last recommendations))]]
        (transaction-row r :last? last?))
      [:tr [:td.has-text-centered.has-text-gray {:colspan 7 }
            [:i "That's the last of 'em!"]]])))

(defn transaction-rows [{:keys [session identity route-params clients]}]
  (html-response (transaction-rows* {:clients clients
                                     :identity        identity
                                     :after           (:after route-params)})))

(defn insight-table* [{:keys [clients identity]}]
  (let [recommendations (transaction-recommendations identity clients)]
    (com/data-grid-card {:id "insight-table"
                         :title "Transaction Insights"
                         :route :transaction-insight-table
                         :paginate? false
                         :total (count recommendations)
                         :action-buttons nil
                         :rows (for [r    recommendations
                                     :let [last? (= r (last recommendations))]]
                                 (transaction-row r :last? last?))
                         :headers [(com/data-grid-header {:style {:width "10em"}} "Client")
                                   (com/data-grid-header {:style {:width "15em"}} "Account")
                                   (com/data-grid-header {:style {:width "8em"}} "Date")
                                   (com/data-grid-header {} "Description")
                                   (com/data-grid-header {:style {:width "8em"}} "Amount")
                                   (com/data-grid-header {})]})))

(defn insight-table [{:keys [session identity clients]}]
  (html-response (insight-table* {:clients clients
                                  :identity identity})))

(defn page [{:keys [identity matched-route session clients] :as request}]
  (base-page
   request
   (com/page {:nav com/main-aside-nav
              :client-selection (:client-selection request)
              :client (:client request)
              :clients (:clients request)
              :identity (:identity request)
              :app-params {:hx-get     (bidi/path-for ssr-routes/only-routes
                                                      :transaction-insights)
                           :hx-trigger "clientSelected from:body"
                           :hx-select "#app-contents"
                           :hx-swap    "outerHTML swap:300ms"}
              :request request}
             (com/breadcrumbs {}
                              [:a {:href (bidi/path-for client-routes/routes
                                                        :transactions)}
                               "Transactions"]
                              [:a {:href (bidi/path-for ssr-routes/only-routes
                                                        :transaction-insights)}
                               "Insights"])
             (insight-table* {:clients clients
                              :identity identity}))

   "Transaction Insights"))
