(ns auto-ap.ssr.admin.accounts
  (:require
   [auto-ap.datomic
    :refer [add-sorter-fields apply-pagination apply-sort-3 audit-transact
            conn merge-query pull-attr pull-many query2]]
   [auto-ap.query-params :as query-params :refer [wrap-copy-qp-pqp]]
   [auto-ap.routes.utils
    :refer [wrap-admin wrap-client-redirect-unauthenticated]]
   [auto-ap.solr :as solr]
   [auto-ap.ssr-routes :as ssr-routes]
   [auto-ap.ssr.components :as com]
   [auto-ap.ssr.form-cursor :as fc]
   [auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
   [auto-ap.ssr.hx :as hx]
   [auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
   [auto-ap.ssr.payments :refer [wrap-status-from-source]]
   [auto-ap.ssr.svg :as svg]
   [auto-ap.ssr.utils
    :refer [apply-middleware-to-all-handlers clj-date-schema
            default-grid-fields-schema entity-id field-validation-error
            form-validation-error html-response main-transformer many-entity
            modal-response ref->enum-schema ref->select-options strip temp-id
            wrap-entity wrap-form-4xx-2 wrap-merge-prior-hx
            wrap-schema-enforce]]
   [bidi.bidi :as bidi]
   [clojure.string :as str]
   [datomic.api :as dc]
   [malli.core :as mc]))

(def query-schema (mc/schema
                   [:maybe
                    (into [:map {:date-range [:date-range :start-date :end-date]}
                           [:type {:optional true} [:maybe (ref->enum-schema "account-type")]]
                           [:name {:optional true} [:maybe [:string {:decode/string strip}]]]
                           [:code {:optional true} [:maybe nat-int?]]]
                          default-grid-fields-schema)]))
(defn filters [request]
  [:form {"hx-trigger"         "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
          "hx-get"             (bidi/path-for ssr-routes/only-routes
                                  :admin-account-table)
          "hx-target"          "#entity-table"
          "hx-indicator"       "#entity-table"}

   [:fieldset.space-y-6 
    (com/field {:label "Name"}
               (com/text-input {:name        "name"
                                :id          "name"
                                :class       "hot-filter"
                                :value       (:name (:query-params request))
                                :placeholder "Cash"
                                :size        :small}))

    (com/field {:label "Code"}
               (com/text-input {:name        "code"
                                :id          "code"
                                :class       "hot-filter"
                                :value       (:code (:query-params request))
                                :placeholder "11101"
                                :size        :small}))
    (com/field {:label "Type"}
               (com/radio-card {:size    :small
                                :name    "type"
                                :value (:type (:query-params request))
                                :options [{:value   ""
                                           :content "All"}
                                          {:value   "dividend"
                                           :content "Dividend"}
                                          {:value   "asset"
                                           :content "Asset"}
                                          {:value   "equity"
                                           :content "Equity"}
                                          {:value   "liability"
                                           :content "Liability"}
                                          {:value   "expense"
                                           :content "Expense"}
                                          {:value   "revenue"
                                           :content "Revenue"}
                                          {:value   "none"
                                           :content "None"}]}))]])

(def default-read '[:db/id
                    :account/code
                    :account/name
                    :account/numeric-code
                    :account/location
                    {[:account/type :xform iol-ion.query/ident] [:db/ident]
                     [:account/invoice-allowance :xform iol-ion.query/ident] [:db/ident]
                     [:account/vendor-allowance :xform iol-ion.query/ident] [:db/ident]
                     [:account/applicability :xform iol-ion.query/ident] [:db/ident]
                     :account/client-overrides [{:account-client-override/client [:client/name :db/id]}
                                                :account-client-override/name
                                                :db/id]}])

(defn fetch-ids [db request]
  (let [query-params (:query-params request)
        query (cond-> {:query {:find  []
                               :in    '[$]
                               :where '[]}
                       :args  [db]}
                (:sort query-params) (add-sorter-fields {"name" ['[?e :account/name ?n]
                                                                 '[(clojure.string/upper-case ?n) ?sort-name]]
                                                         "code" ['[(get-else $ ?e :account/numeric-code 0) ?sort-code]]
                                                         "type" ['[?e :account/type ?t]
                                                                 '[?t :db/ident ?ti]
                                                                 '[(name ?ti) ?sort-type]]}
                                                        query-params)
                (some->> query-params :name not-empty)
                (merge-query {:query {:find  []
                                      :in ['?ns]
                                      :where ['[?e :account/name ?an]
                                              '[(clojure.string/upper-case ?an) ?upper-an]
                                              '[(clojure.string/includes? ?upper-an ?ns)]]}
                              :args [(str/upper-case (:name query-params))]})
       
                (some->> query-params :code)
                (merge-query {:query {:find  []
                                      :in ['?nc]
                                      :where ['[?e :account/numeric-code ?nc]]}
                              :args [(:code query-params)]})
       
                (some->> query-params :type)
                (merge-query {:query {:find  []
                                      :in    ['?r]
                                      :where ['[?e :account/type ?r] ]}
                              :args  [(some->> query-params :type)]})
       
                true
                (merge-query {:query {:find ['?sort-default '?e]
                                      :where ['[?e :account/numeric-code ?un]
                                              '[(clojure.string/upper-case ?un) ?sort-default]]}}))]
    (cond->> (query2 query)
      true (apply-sort-3 query-params)
      true (apply-pagination query-params))))

(defn hydrate-results [ids db _]
  (let [results (->> (pull-many db default-read ids)
                     (group-by :db/id))
        refunds (->> ids
                     (map results)
                     (map first))]
    refunds))

(defn fetch-page [request]
  (let [db                                           (dc/db conn)
        {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]

    [(->> (hydrate-results ids-to-retrieve db request))
     matching-count]))

(def grid-page
  (helper/build {:id                 "entity-table"
                 :nav                com/admin-aside-nav
                 :page-specific-nav  filters
                 :fetch-page         fetch-page
                 :action-buttons (fn [_]
                                   [(com/button {:hx-get    (str (bidi/path-for ssr-routes/only-routes
                                                                                :admin-account-new-dialog))
                                                 :color     :primary}
                                                "New Account")])
                 :row-buttons (fn [_ entity]
                                [(com/icon-button {:hx-get (str (bidi/path-for ssr-routes/only-routes
                                                                               :admin-account-edit-dialog
                                                                               :db/id (:db/id entity)))}
                                                  svg/pencil)])
                 :breadcrumbs        [[:a {:href (bidi/path-for ssr-routes/only-routes
                                                                :admin)}
                                       "Admin"]

                                      [:a {:href (bidi/path-for ssr-routes/only-routes
                                                                :admin-accounts)}
                                       "Accounts"]]
                 :title       "Accounts"
                 :entity-name "Account"
                 :query-schema query-schema
                 :route       :admin-account-table
                 :headers     [{:key      "code"
                                :name     "Code"
                                :sort-key "code"
                                :render   :account/numeric-code}
                               {:key      "name"
                                :name     "Name"
                                :sort-key "name"
                                :render   :account/name}
                               {:key      "type"
                                :name     "Type"
                                :sort-key "type"
                                :render   #(some->> % :account/type name (com/pill {:color :primary}))}
                               {:key      "location"
                                :name     "Location"
                                :sort-key "location"
                                :render   :account/location}]}))

(def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page))

(defn account-save [{:keys [form-params request-method] :as request}]
  (let [entity (cond-> form-params
                 (= :post request-method) (assoc :db/id "new" :account/default-allowance :allowance/allowed))
        _      (cond (= :post request-method)
                     (when (seq (dc/q '[:find ?x :in $ ?nc :where [?x :account/numeric-code ?nc]] (dc/db conn) (:account/numeric-code entity)))
                       (field-validation-error (format "The code %d is already in use." (:account/numeric-code entity))
                                               [:account/numeric-code]
                                               :form-params form-params)))
        _      (some->> form-params
                   :account/client-overrides
                   (group-by :account-client-override/client)
                   (filter (fn [[_ overrides]]
                             (> (count overrides) 1)))
                   (map first)
                   seq
                   (#(form-validation-error (format "Client(s) %s have more than one override."
                                                    (str/join ", "
                                                              (map (fn [client]
                                                                     (format "'%s'" (pull-attr (dc/db conn)
                                                                                               :client/name
                                                                                               (-> client)))
                                                                     ) %)))
                                            :form-params form-params)) ;; TODO shouldnt need to bubble this through. See if we can eliminate the passing of form and last-form.
                   )
        {:keys [tempids]} (audit-transact [[:upsert-entity (cond-> entity
                                                             (:account/numeric-code entity) (assoc :account/code (str (:account/numeric-code entity))))]]
                                          (:identity request))
        updated-account   (dc/pull (dc/db conn)
                                   default-read
                                   (or (get tempids (:db/id entity)) (:db/id entity)))]
    (solr/index-documents-raw solr/impl
                              "accounts"
                              (into [{"id"            (:db/id updated-account)
                                      "account_id"    (:db/id updated-account)
                                      "name"          (:account/name updated-account)
                                      "numeric_code"  (:account/numeric-code updated-account)
                                      "location"      (:account/location updated-account)
                                      "applicability" (some-> updated-account :account/applicability clojure.core/name)}]
                                    (for [o (:account/client-overrides updated-account)]
                                      {"id"                         (:db/id o)
                                       "account_id"                 (:db/id updated-account)
                                       "name"                       (:account-client-override/name o)
                                       "numeric_code"               (:account/numeric-code updated-account)
                                       "location"                   (:account/location updated-account)
                                       "applicability"              (clojure.core/name (:account/applicability updated-account))
                                       "client_id"                  (:db/id (:account-client-override/client o))
                                       "account_client_override_id" (:db/id o)})))
    (html-response
      (row* identity updated-account {:flash? true})
      :headers (cond-> {"hx-trigger"  "modalclose"}
                 (= :post request-method) (assoc "hx-retarget" "#entity-table tbody"
                                                 "hx-reswap" "afterbegin")
                 (= :put request-method) (assoc "hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id updated-account)))))))

(defn client-override* [override]
  (com/data-grid-row (-> {:x-ref    "p"
                          :data-key "show"
                          :x-data   (hx/json {:show (boolean (not (fc/field-value (:new? override))))})}
                         hx/alpine-mount-then-appear)
                     (fc/with-field :db/id
                       (com/hidden {:name  (fc/field-name)
                                    :value (fc/field-value)}))
                     (fc/with-field :account-client-override/client
                       (com/data-grid-cell {}
                                           (com/validated-field {:errors (fc/field-errors)}
                                                                (com/typeahead {:name        (fc/field-name)
                                                                                  :placeholder "Search..."
                                                                                  :class "w-96"
                                                                                  :url         (bidi/path-for ssr-routes/only-routes
                                                                                                              :company-search)
                                                                                  :value       (fc/field-value)
                                                                                  :content-fn  #(pull-attr (dc/db conn) :client/name %)}))))
                     (fc/with-field :account-client-override/name
                       (com/data-grid-cell
                         {}
                         (com/validated-field {:errors (fc/field-errors)}
                                              (com/text-input {:name  (fc/field-name)
                                                               :class "w-96"
                                                               :value (fc/field-value)}))))
                     (com/data-grid-cell {:class "align-top"}
                                         (com/a-icon-button {"@click.prevent.stop" "show=false; setTimeout(() => $refs.p.remove(), 500)"} svg/x))))

(defn dialog* [{:keys [entity form-params form-errors]}]
  (fc/start-form form-params form-errors
    [:div {:x-data (hx/json {"accountName" (or (:account/name form-params) (:account/numeric-code entity))
                             "accountCode" (or  (:account/numeric-code form-params) (:account/numeric-code entity) )})
           :hx-target "this"
           }
     (com/modal
      {} 
       [:form (-> {:hx-ext        "response-targets"
                   :hx-swap       "outerHTML swap:300ms"
                   :hx-target-400 "#form-errors .error-content" }
                  (assoc (if (:db/id entity)
                           :hx-put
                           :hx-post)
                         (str (bidi/path-for ssr-routes/only-routes
                                             :admin-transaction-rule-edit-save))))
        [:fieldset {:class "hx-disable"}
         (com/modal-card
           {:class "md:h-[600px]"}
           [:div.flex [:div.p-2 "Account"] [:p.ml-2.rounded.bg-gray-200.p-2.dark:bg-gray-600
                                            [:span {:x-text "accountCode"}]
                                            " - "
                                            [:span {:x-text "accountName"}]]]
           [:div.space-y-1
            (when-let [id (:db/id entity)]
              (com/hidden {:name  "db/id"
                           :value id}))

            (fc/with-field :account/numeric-code
              (if (nil? (:db/id entity))
                (com/validated-field {:label  "Numeric code"
                                      :errors (fc/field-errors)}
                                     (com/text-input {:name      (fc/field-name)
                                                      :value     (fc/field-value)
                                                      :x-model   "accountCode"
                                                      :autofocus true
                                                      :class     "w-32"}))
                (com/hidden {:name  (fc/field-name)
                             :value (fc/field-value)})))
            (fc/with-field :account/name
              (com/validated-field {:label  "Name"
                                    :errors (fc/field-errors)}
                                   (com/text-input {:name    (fc/field-name)
                                                    :x-model "accountName"

                                                    :class "w-64"
                                                    :value (fc/field-value)})))
            (fc/with-field :account/type
              (com/validated-field {:label  "Account Type"
                                    :errors (fc/field-errors)}
                                   (com/select {:name    (fc/field-name)
                                                :class   "w-36"
                                                :id      "type"
                                                :value   (some-> (fc/field-value) name)
                                                :options (ref->select-options "account-type")})))
            (fc/with-field :account/location
              (com/validated-field {:label  "Location"
                                    :errors (fc/field-errors)}
                                   (com/text-input {:name  (fc/field-name)
                                                    :class "w-16"
                                                    :value (fc/field-value)})))

            [:div.flex.flex-wrap.gap-4
             (fc/with-field :account/invoice-allowance
               (com/validated-field {:label  "Invoice Allowance"
                                     :errors (fc/field-errors)}
                                    (com/select {:name    (fc/field-name)
                                                 :value   (some-> (fc/field-value) name)
                                                 :class   "w-36"
                                                 :options (ref->select-options "allowance")})))
             (fc/with-field :account/vendor-allowance
               (com/validated-field {:label  "Vendor Allowance"
                                     :errors (fc/field-errors)}
                                    (com/select {:name    (fc/field-name)
                                                 :class   "w-36"
                                                 :value   (some-> (fc/field-value) name)
                                                 :options (ref->select-options "allowance")})))]
            (fc/with-field :account/applicability
              (com/validated-field {:label  "Applicability"
                                    :errors (fc/field-errors)}
                                   (com/select {:name    (fc/field-name)
                                                :class   "w-36"
                                                :value   (some-> (fc/field-value) name)
                                                :options (ref->select-options "account-applicability")})))

            (fc/with-field :account/client-overrides
              
              (com/field {:label "Client Overrides" :id "client-overrides"}

                         (com/data-grid {:headers [(com/data-grid-header {} "Client")
                                                   (com/data-grid-header {} "Account name")
                                                   (com/data-grid-header {})]
                                         :id      "client-override-table"}
                                        (fc/cursor-map
                                          #(client-override* %))

                                        (com/data-grid-new-row {:colspan 3
                                                                :index   (count (fc/field-value))
                                                                :hx-get  (bidi/path-for ssr-routes/only-routes
                                                                                           :admin-account-client-override-new)}
                                                               "New override"))))]
           [:div
            (com/form-errors {:errors (:errors fc/*form-errors*)})
            (com/validated-save-button {:errors (seq form-errors)}
                                       "Save account")])]])]))

(defn new-client-override [{ {:keys [index]} :query-params}]
  (html-response 
    (fc/start-form-with-prefix
        [:account/client-overrides (or index 0)]
        {:db/id (str (java.util.UUID/randomUUID))
         :new?  true}
        []
      (client-override* fc/*current*))))

(def form-schema (mc/schema
                      [:map 
                       [:db/id {:optional true} [:maybe entity-id]]
                       [:account/numeric-code {:optional true} [:maybe :int]]
                       [:account/name [:string {:min 1 :decode/string strip}]]
                       [:account/location {:optional true} [:maybe [:string {:decode/string strip}]]]
                       [:account/type (ref->enum-schema "account-type")]
                       [:account/applicability (ref->enum-schema "account-applicability")] ;
                       [:account/invoice-allowance (ref->enum-schema "allowance")]
                       [:account/vendor-allowance (ref->enum-schema "allowance")]
                       [:account/client-overrides {:optional true}
                        [:maybe 
                         (many-entity {}
                                      [:db/id [:or entity-id temp-id]]
                                      [:account-client-override/client entity-id]
                                      [:account-client-override/name [:string {:min 2 :decode/string strip}]])]]]))

(defn account-dialog [{:keys [entity form-params form-errors]}]
  (modal-response (dialog* {:entity      entity
                           :form-params (or (when (seq form-params)
                                              form-params)
                                            (when entity
                                              (mc/decode form-schema entity main-transformer))
                                            {})
                           :form-errors form-errors})))




(def key->handler
  (apply-middleware-to-all-handlers
   (->>
    {:admin-accounts                    (helper/page-route grid-page)
     :admin-account-table               (helper/table-route grid-page)
     :admin-account-client-override-new (-> new-client-override
                                            (wrap-schema-enforce :query-schema [:map
                                                                                [:index {:optional true
                                                                                         :default  0} [nat-int? {:default 0}]]])
                                            wrap-admin wrap-client-redirect-unauthenticated)
     :admin-account-save                (-> account-save
                                            (wrap-entity [:form-params :db/id] default-read)
                                            (wrap-schema-enforce :form-schema form-schema)
                                            (wrap-nested-form-params)
                                            (wrap-form-4xx-2 (wrap-entity account-dialog [:form-params :db/id] default-read)))
     :admin-account-edit-dialog         (-> account-dialog
                                            (wrap-entity [:route-params :db/id] default-read)
                                            (wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
     :admin-account-new-dialog          account-dialog})
   (fn [h]
     (-> h
         (wrap-copy-qp-pqp)
         (wrap-apply-sort grid-page)
         (wrap-merge-prior-hx)
         (wrap-status-from-source)
         (wrap-schema-enforce :query-schema query-schema)
         (wrap-schema-enforce :hx-schema query-schema)
         (wrap-admin)
         (wrap-client-redirect-unauthenticated)))))
