;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC

(ns app.setup
  "Initial data setup of instance."
  (:require
   [app.common.logging :as l]
   [app.common.spec :as us]
   [app.common.uuid :as uuid]
   [app.db :as db]
   [app.main :as-alias main]
   [app.setup.keys :as keys]
   [app.setup.templates]
   [buddy.core.codecs :as bc]
   [buddy.core.nonce :as bn]
   [clojure.spec.alpha :as s]
   [integrant.core :as ig]))

(defn- generate-random-key
  []
  (-> (bn/random-bytes 64)
      (bc/bytes->b64u)
      (bc/bytes->str)))

(defn- retrieve-all
  [conn]
  (->> (db/query conn :server-prop {:preload true})
       (filter #(not= "secret-key" (:id %)))
       (map (fn [row]
              [(keyword (:id row))
               (db/decode-transit-pgobject (:content row))]))
       (into {})))

(defn- handle-instance-id
  [instance-id conn read-only?]
  (or instance-id
      (let [instance-id (uuid/random)]
        (when-not read-only?
          (try
            (db/insert! conn :server-prop
                        {:id "instance-id"
                         :preload true
                         :content (db/tjson instance-id)})
            (catch Throwable cause
              (l/warn :hint "unable to persist instance-id"
                      :instance-id instance-id
                      :cause cause))))
        instance-id)))

(s/def ::main/key ::us/string)
(s/def ::main/props
  (s/map-of ::us/keyword some?))

(defmethod ig/pre-init-spec ::props [_]
  (s/keys :req [::db/pool]
          :opt [::main/key]))

(defmethod ig/init-key ::props
  [_ {:keys [::db/pool ::main/key] :as cfg}]
  (db/with-atomic [conn pool]
    (db/xact-lock! conn 0)
    (when-not key
      (l/warn :hint (str "using autogenerated secret-key, it will change on each restart and will invalidate "
                         "all sessions on each restart, it is hightly recommended setting up the "
                         "PENPOT_SECRET_KEY environment variable")))

    (let [secret (or key (generate-random-key))]
      (-> (retrieve-all conn)
          (assoc :secret-key secret)
          (assoc :tokens-key (keys/derive secret :salt "tokens"))
          (update :instance-id handle-instance-id conn (db/read-only? pool))))))
