(ns auto-ap.ssr.ledger.cash-flows
  (:require
   [amazonica.aws.s3 :as s3]
   [auto-ap.datomic
    :refer [conn pull-many]]
   [auto-ap.graphql.utils :refer [assert-can-see-client]]
   [auto-ap.ledger :refer [build-account-lookup]]
   [auto-ap.ledger.reports :as l-reports]
   [auto-ap.logging :as alog]
   [auto-ap.pdf.ledger :refer [table->pdf]]
   [auto-ap.permissions :refer [wrap-must]]
   [auto-ap.routes.ledger :as route]
   [auto-ap.routes.utils
    :refer [wrap-client-redirect-unauthenticated]]
   [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.ledger.report-table :refer [cell-count concat-tables table]]
   [auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
   [auto-ap.ssr.svg :as svg]
   [auto-ap.ssr.ui :refer [base-page]]
   [auto-ap.ssr.utils
    :refer [apply-middleware-to-all-handlers clj-date-schema html-response
            modal-response wrap-form-4xx-2 wrap-schema-enforce]]
   [auto-ap.time :as atime]
   [bidi.bidi :as bidi]
   [clj-pdf.core :as pdf]
   [clj-time.coerce :as coerce]
   [clj-time.core :as time]
   [clojure.java.io :as io]
   [clojure.string :as str]
   [config.core :refer [env] :as env]
   [datomic.api :as dc]
   [iol-ion.utils :refer [by]]
   [malli.core :as mc])
  (:import
   [java.util UUID]
   [org.apache.commons.io.output ByteArrayOutputStream]))


(def query-schema (mc/schema
                   [:maybe [:map
                                  [:client {:unspecified/value :all}
                                   [:or 
                                    [:enum :all]
                                    [:vector {:coerce? true :min 1}
                                     [:entity-map {:pull [:db/id :client/name]}]]]]
                                    
                                  [:periods {:unspecified/fn (fn [] (let [now (atime/local-now)] 
                                                                      [{:start (atime/as-local-time (time/date-time (time/year now)
                                                                                                               1
                                                                                                               1))
                                                                                  :end (atime/local-now)}])

                                                               )}
                                   [:vector {:coerce? true}
                                    [:map 
                                     [:start clj-date-schema]
                                     [:end clj-date-schema]]]]]]))
;; TODO 
;; 1. Rerender form when running
;; 2. Don't throw crazy errors when missing a field
;; 3. General cleanup of the patterns  in run-balance-sheet
;; 4. Review ledger dialog

(defn get-report [{ {:keys [periods client] :as qp} :form-params :as request}]
  (when (and (seq periods) client)
    (let [client (if (= :all client) (take 5 (:clients request)) client)
          client-ids (map :db/id client)
          _ (doseq [client-id client-ids]
              (assert-can-see-client (:identity request) client-id))
          
          lookup-account (->> client-ids
                              (map (fn build-lookup [client-id]
                                     [client-id (build-account-lookup client-id)]))
                              (into {}))
          data (into []
                     (for [client-id client-ids
                           p periods
                           [client-id account-id location debits credits balance count] (iol-ion.query/detailed-account-snapshot (dc/db conn) client-id (coerce/to-date (time/plus (:end p) (time/days 1))))
                           :let [account ((or (lookup-account client-id) {}) account-id)]]
                       {:client-id client-id
                        :account-id account-id
                        :location location
                        :debits debits
                        :credits credits
                        :count count
                        :amount balance
                        :account-type (:account_type account)
                        :numeric-code (:numeric_code account)
                        :name (:name account)
                        :period {:start ( coerce/to-date (:start p))  :end ( coerce/to-date (:end p))}}))
          args          (assoc (:form-params request)
                               :periods (map (fn [d] {:start ( coerce/to-date (:start d))  :end ( coerce/to-date (:end d))}) periods))
          clients       (pull-many (dc/db conn) [:client/code :client/name :db/id] client-ids)
          
          pnl-data      (l-reports/->PNLData args data (by :db/id :client/code clients))
          report        (l-reports/summarize-cash-flows pnl-data)]
      (alog/info ::cash-flows :params args)
      {:data report
       :report report})))

(defn maybe-trim-clients [request client]
  (if (= :all client)
    (cond-> {:client (take 20 (:clients request))}
      (> (count (:clients request)) 20)
      (assoc :warning "You requested a report with more than 20 clients. This report will only contain the first 20."))
    {:client client}))

(defn cash-flows* [{ {:keys [periods client] } :form-params :as request}]
  [:div#report 
   (when (and periods client)
     (let [{:keys [client warning]} (maybe-trim-clients request client)
           {:keys [data report]} (get-report (assoc-in request [:form-params :client] client))
           client-count (count (set (map :client-id (:data data)))) 
           table-contents (concat-tables (:details report))]
       (list 
        [:div.text-2xl.font-bold.text-gray-600 (str "Cash flows - " (str/join ", " (map :client/name client)))]
        (table {:widths (into [20] (take (dec (cell-count table-contents))
                                         (mapcat identity
                                                 (repeat
                                                  [13 6]))))
                :investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate)
                :table       table-contents
                :warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))}))))])

(defn form* [request & children]
  (let [params (or (:query-params request) {})]
    (fc/start-form
      params
     (:form-errors request)
     [:div#cash-flows-form.flex.flex-col.gap-4.mt-4
      [:div.flex.gap-8 
       [:form {:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/run-cash-flows) 
               :hx-target "#cash-flows-form"
               :hx-swap "outerHTML"
               :hx-disabled-elt "find fieldset"}
        [:fieldset 
         [:div.flex.gap-8 {:x-data (hx/json {})}
          (fc/with-field :client
            (com/validated-inline-field 
             {:label "Customers" :errors (fc/field-errors)}
             (com/multi-typeahead {:name        (fc/field-name)
                                   :placeholder "Search for companies..."
                                   :class "w-64"
                                   :id          "client"
                                   :url (bidi/path-for ssr-routes/only-routes :company-search)
                                   :value       (fc/field-value)
                                   :value-fn :db/id
                                   :content-fn :client/name})))
          (fc/with-field :periods
            (com/validated-inline-field {:label "Periods"
                                         :errors (fc/field-errors)} 
                                        (com/periods-dropdown {:value (fc/field-value)})))
          
          (com/button {:color :primary :class "w-32"}
                      "Run")
          (com/button {:formaction (bidi.bidi/path-for ssr-routes/only-routes ::route/export-cash-flows) } "Export PDF")]]]]
      children])))

(defn form [request]
  (html-response (form* (assoc request :query-params (:form-params request))
                        (cash-flows* request))
                 :headers {"hx-retarget" "#cash-flows-form"
                           "hx-push-url" (str "?" (:query-string request))}))

(defn cash-flows [request]
  (base-page
   request
   (com/page {:nav              com/main-aside-nav
              
              :client-selection (:client-selection request)
              :clients          (:clients request)
              :client           (:client request)
              :identity         (:identity request)
              :request request}
             (apply com/breadcrumbs {} [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
                                         "Ledger"]])
             (form* request))
   "Cash Flows"))

(defn make-cash-flows-pdf [request report]
  (let [ output-stream (ByteArrayOutputStream.)
        date (:periods (:form-params request))]
    (pdf/pdf
      (-> [{:left-margin 10 :right-margin 10 :top-margin 15 :bottom-margin 15
            :size        :letter
            :font        {:size     6
                          :ttf-name "fonts/calibri-light.ttf"}}
           [:heading (str "Balance Sheet - " (str/join ", " (map :client/name (seq (:client (:form-params request))))))]]

          (conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
          (conj 
            (table->pdf (concat-tables (:details report))
                        (into [20 ] (mapcat  identity (repeat (count date) [ 13 13 13]))))))
      output-stream)
    (.toByteArray output-stream)))

(defn join-names [client-ids]
  (str/replace (->> client-ids (pull-many (dc/db conn) [:client/name]) (map :client/name) (str/join "-")) #"[^\w]" "_"))

(defn cash-flows-args->name [request]
  (let [date (atime/unparse-local
                   (:date (:query-params request))
                   atime/iso-date)
        name    (->> request :query-params :client (map :db/id) join-names)]
    (format "Balance-sheet-%s-for-%s" date name)))

(defn print-cash-flows [request]
  (let [uuid     (str (UUID/randomUUID))
        {:keys [client warning]} (maybe-trim-clients request (:client (:form-params request)))
        request  (assoc-in request [:form-params :client] client)
        pdf-data (make-cash-flows-pdf request (:report (get-report request)))
        name     (cash-flows-args->name request)
        key      (str "reports/cash-flows/" uuid "/" name ".pdf")
        url      (str "https://" (:data-bucket env) "/" key)]
    (s3/put-object :bucket-name (:data-bucket env/env)
                   :key key
                   :input-stream (io/make-input-stream pdf-data {})
                   :metadata {:content-length (count pdf-data)
                              :content-type   "application/pdf"})
    @(dc/transact conn
                  [{:report/name    name
                    :report/client  (map :db/id client)
                    :report/key     key
                    :report/url     url
                    :report/creator (:user (:identity request))
                    :report/created (java.util.Date.)}])
    {:report/name name
     :report/url  url}))

;; TODO PRINT WARNING
(defn export [request]
  (modal-response
   (com/modal {}
              (com/modal-card
               {}
               "Ready!"
               (com/modal-body {}
                               (let [bs (print-cash-flows request)]
                                 [:div.flex.flex-col.mt-4.space-y-4.items-center
                                  [:a {:href (:report/url bs)}
                                   [:div.w-24.h-24.bg-green-50.rounded-full.p-4.text-green-300 {:class " hover:scale-110 transition duration-100"}

                                    svg/download]]
                                  [:span.text-gray-800
                                   "Click "
                                   (com/link {:href (:report/url bs)} "here")
                                   " to download"]]))
                                  
               nil))
   :headers (-> {}
                (assoc "hx-retarget" ".modal-stack")
                (assoc "hx-reswap" "beforeend"))))

(def key->handler
  (apply-middleware-to-all-handlers
   (->
    {::route/cash-flows (-> cash-flows
                                   (wrap-schema-enforce :query-schema query-schema)
                                   (wrap-form-4xx-2 cash-flows))
     ::route/run-cash-flows (-> form
                                   (wrap-schema-enforce :form-schema query-schema)
                                   (wrap-form-4xx-2 form))
     ::route/export-cash-flows (-> export
                                   (wrap-schema-enforce :form-schema query-schema)
                                   (wrap-form-4xx-2 form))})
    
   (fn [h]
     (-> h
         #_(wrap-merge-prior-hx)
         (wrap-must {:activity :read :subject :cash-flows})
         (wrap-nested-form-params)
         (wrap-client-redirect-unauthenticated)))))
