(ns auto-ap.ssr.company.reports.expense 
  (:require [auto-ap.datomic :refer [conn merge-query]]
            [auto-ap.graphql.utils :refer [extract-client-ids]]
            [auto-ap.logging :as alog]
            [auto-ap.ssr-routes :as ssr-routes]
            [auto-ap.ssr.components :as com]
            [auto-ap.ssr.form-cursor :as fc]
            [auto-ap.ssr.hx :as hx]
            [auto-ap.ssr.ui :refer [base-page]]
            [auto-ap.ssr.utils :refer [apply-middleware-to-all-handlers
                                       clj-date-schema html-response
                                       wrap-schema-enforce]]
            [auto-ap.time :as atime]
            [auto-ap.utils :refer [by]]
            [bidi.bidi :as bidi]
            [cemerick.url :as url]
            [clj-time.coerce :as coerce]
            [clj-time.core :as time]
            [datomic.api :as dc]
            [hiccup2.core :as hiccup]))

(defn lookup-breakdown-data [request]
    (let [query (cond-> {:query '{:find [?cn ?user-date (sum ?amt)]
                                  :with [?e]
                                  :in [$ [?clients ?start ?end]]
                                  :where
                                  [[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
                                   (not [?e :invoice/status :invoice-status/voided])
                                   [?e :invoice/date ?d]
                                   [?e :invoice/client ?c]
                                   [?e :invoice/expense-accounts ?iea]
                                   [?iea :invoice-expense-account/amount ?amt]
                                   [?c :client/name ?cn]
                                   [(clj-time.coerce/to-date-time ?d) ?user-date]]}
                         :args 
                         [(dc/db conn)
                          [(extract-client-ids (:clients request)
                                               (:client-id request)
                                               (when (:client-code request)
                                                 [:client/code (:client-code request)]))
                           (some-> (time/plus (time/now) (time/days -65)) coerce/to-date)
                           (some-> (time/now) coerce/to-date)]]}

                  (:vendor-id (:query-params request))
                  (merge-query {:query '{:in [?v]
                                        :where [ [?e :invoice/vendor ?v]]}
                                :args [ (:db/id (:vendor-id (:query-params request)))]})
                  
                  (:account-id (:query-params request))
                  (merge-query {:query '{:in [?a]
                                        :where [ [?iea :invoice-expense-account/account ?a]]}
                                :args [ (:db/id (:account-id (:query-params request)))]}))]
        
      (dc/query query)))

(defn lookup-invoice-total-data [request]
    (let [start (:start-date (:query-params request) (time/plus (time/now) (time/days -30)))
          end (:end-date (:query-params request) (time/now))
          query (cond-> {:query '{:find [?cn ?vn (sum ?t)]
                                  :with [ ?e]
                                  :in [$ [?clients ?start ?end]]
                                  :where
                                  [[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]
                                   (not [?e :invoice/status :invoice-status/voided])
                                   [?e :invoice/client ?c]
                                   [?e :invoice/total ?t]
                                   [?e :invoice/vendor ?v]
                                   [?v :vendor/name ?vn]
                                   [?c :client/name ?cn]
                                   ]}
                         :args 
                         [(dc/db conn)
                          [(extract-client-ids (:clients request)
                                               (:client-id request)
                                               (when (:client-code request)
                                                 [:client/code (:client-code request)]))
                           (some-> start coerce/to-date)
                           (some-> end coerce/to-date)]]})]
        
      (dc/query query)))

(defn week-seq 
  ([c] (week-seq c (atime/last-monday)))
  ([c starting] (reverse (for [n (range c)
                               :let [start (time/minus starting (time/weeks n))
                                     end (time/minus starting (time/weeks (dec n)))]]
                           [(atime/as-local-time (coerce/to-date-time start)) (atime/as-local-time (coerce/to-date-time end))]))))


(defn- best-week [d weeks]
  (reduce
   (fn [acc [start end]]
     (if (and (time/after? d start)
              (time/before? d end))
       (reduced [start end])
       nil))
   nil
   weeks))


(defn expense-breakdown-card* [request]
  (com/card {:class "w-full h-full" :id "expense-breakdown-report"}
            [:div {:class "flex flex-col px-8 py-8 space-y-3 w-full h-full"}
             
             [:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-breakdown-card)
                     :hx-trigger "change"
                     :hx-target "#expense-breakdown-report"
                     :hx-swap "outerHTML"}
              (fc/start-form
               (:query-params request)
               (:form-errors request)
               [:div.flex.justify-between
                [:h1.text-2xl.mb-3.font-bold "Expense breakdown report, last 8 weeks"]
                [:div.flex.gap-2
                 (fc/with-field :vendor-id
                   (com/validated-field {:label "Vendor"
                                         :errors (fc/field-errors)}
                                        (com/typeahead {:name        (fc/field-name)
                                                        :class "w-64"
                                                        :url (bidi/path-for ssr-routes/only-routes :vendor-search)
                                                        :value       (fc/field-value)
                                                        :value-fn :db/id
                                                        :content-fn :vendor/name})))
                 (fc/with-field :account-id
                   (com/validated-field {:label "Account"
                                         :errors (fc/field-errors)}
                                        (com/typeahead {:name        (fc/field-name)
                                                        :class "w-64"
                                                        :url (bidi/path-for ssr-routes/only-routes :account-search)
                                                        :value       (fc/field-value)
                                                        :value-fn :db/id
                                                        :content-fn :account/name})))]])]
             [:div.flex-grow
              (let [data (lookup-breakdown-data request)
                    distinct-accounts (->> data
                                           (reduce
                                            (fn [acc [an _ amount]]
                                              (update acc an (fnil + 0.0) amount))
                                            {})
                                           (sort-by last)
                                           (reverse)
                                           (map first)
                                           (take 20))
                    weeks (week-seq 8)
                    x-axis (for [[start end] weeks]
                             (str (iol-ion.query/excel-date (coerce/to-date start))
                                  " - "
                                  (iol-ion.query/excel-date (coerce/to-date end))))

                    lookup (->>
                            (reduce
                             (fn [acc [a d v]]
                               (update-in acc [a (best-week d weeks)] (fnil + 0.0) v))
                             {}
                             data))
                    series (for [ea distinct-accounts]
                             (for [d weeks]
                               (get-in lookup [ea d] 0)))]
                [:canvas {:x-data (hx/json {:chart nil
                                                          :labels x-axis
                                                          :datasets (map (fn [s a] {:label a
                                                                                    :data s
                                                                                    :borderWidth 1})
                                                                         series
                                                                         distinct-accounts)})
                                        :x-init
                                        "new Chart($el, {
                        type: 'bar',
                        data: {
                          labels: labels,
                          datasets: datasets
                        },
                        options: {
                          responsive: true,
                           maintainAspectRatio: false,
                          scales: {
                            y: {
                              beginAtZero: true
                            }
                          }
                        }
                      });"}])]]))

(defn vendor-invoice-total-card* [request]
  (com/content-card {:class "w-full" :id "invoice-totals-report"}
                    [:div {:class "flex flex-col px-8 py-8 space-y-3"}
                     [:div
                      [:h1.text-2xl.mb-3.font-bold "Invoice totals by vendor"]
                      [:form {:hx-get (bidi.bidi/path-for ssr-routes/only-routes :company-expense-report-invoice-total-card )
                              :hx-trigger "change"
                              :hx-target "#invoice-totals-report"
                              :hx-swap "outerHTML"}
                       (fc/start-form
                        (:query-params request)
                        (:form-errors request)
                        [:div.flex.gap-2
                         (fc/with-field :start-date
                           (com/validated-field {:label "Start"
                                                 :errors (fc/field-errors)}
                                                [:div {:class "w-64"}
                                                 (com/date-input {:name        (fc/field-name)
                                                                  :class "w-64"
                                                                  :value       (some-> (fc/field-value)
                                                                                       (atime/unparse-local atime/normal-date)) })]))
                         (fc/with-field :end-date
                           (com/validated-field {:label "End"
                                                 :errors (fc/field-errors)}
                                                [:div {:class "w-64"}
                                                 (com/date-input {:name        (fc/field-name)
                                                                  :class "w-64"
                                                                  :value       (some-> (fc/field-value)
                                                                                       (atime/unparse-local atime/normal-date)) })]))])]
                      [:div {:class "overflow-scroll min-w-full max-h-[700px]"}
                       (let [data (lookup-invoice-total-data request)
                             companies (sort (set (map first data)))
                             vendors (sort (set (map second data)))
                             result (by (juxt first second) last data)
                             ]
                         (com/data-grid
                          {:headers (into
                                     [(com/data-grid-header {:class "sticky left-0 z-60 bg-gray-100"} "Vendor")]
                                     (for [company companies]
                                       (com/data-grid-header {} company)))
                           :thead-params {:class "sticky top-0 z-50"}}
                          (for [vendor vendors]
                            (com/data-grid-row
                             {}
                             (com/data-grid-cell {:class "sticky left-0 z-0 bg-gray-100"}
                                                 vendor)
                             (for [company companies]
                               (com/data-grid-cell
                                {}
                                (or (some->> (get result [company vendor])
                                             (format "$%,.2f" ))
                                    [:span.text-gray-200 "-"])))))))]]]))

(defn page [request]
  (base-page
   request
   (com/page {:nav           com/company-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 :company-expense-report)
                           :hx-trigger "clientSelected from:body"
                           :hx-select "#app-contents"
                           :hx-swap    "outerHTML swap:300ms"}}
             (com/breadcrumbs {}
                              [:a {:href (bidi/path-for ssr-routes/only-routes :company)}
                               "My Company"]
                              [:a {:href (bidi/path-for ssr-routes/only-routes :company-expense-report)}
                               "Expense Report"])
             (expense-breakdown-card* request)
             (vendor-invoice-total-card* request))
   "My Company"))

(defn normalize-query-params [request]
  (-> request
      :query-params
      (update :vendor-id :db/id)
      (update :account-id :db/id)
      (update :start-date #(atime/unparse-local % atime/normal-date))
      (update :end-date #(atime/unparse-local % atime/normal-date))

      url/map->query))

(defn expense-breakdown-card [request]
  (html-response
   (expense-breakdown-card* request)
   :headers {"hx-push-url" (str "?" (normalize-query-params request))}))
(defn invoice-total-card [request]
  (html-response
   (vendor-invoice-total-card* request)
   :headers {"hx-push-url" (str "?" (normalize-query-params request))}))

(def key->handler
  (apply-middleware-to-all-handlers
   {:company-expense-report page
    :company-expense-report-breakdown-card expense-breakdown-card
    :company-expense-report-invoice-total-card invoice-total-card}
   (fn [h]
     (-> h
         (wrap-schema-enforce :query-schema
                              [:map {:default {}}
                               [:start-date {:optional true}
                                [:maybe clj-date-schema]]
                               [:end-date {:optional true}
                                [:maybe clj-date-schema]]
                               [:vendor-id {:optional true}
                                [:maybe
                                 [:entity-map {:pull [:vendor/name :db/id]}]]]
                               [:account-id {:optional true}
                                [:maybe
                                 [:entity-map {:pull [:account/name :db/id]}]]]])))))