(ns metabase-enterprise.advanced-config.file.databases
  (:require
   [clojure.spec.alpha :as s]
   [metabase-enterprise.advanced-config.file.interface :as advanced-config.file.i]
   [metabase.driver.util :as driver.u]
   [metabase.models.database :refer [Database]]
   [metabase.models.setting :refer [defsetting]]
   [metabase.util :as u]
   [metabase.util.log :as log]
   [toucan2.core :as t2]))

Whether to (asynchronously) sync newly created Databases during config-from-file initialization. By default, true, but you can disable this behavior if you want to sync it manually or use SerDes to populate its data model.

(defsetting config-from-file-sync-databases
  :visibility :internal
  :type       :boolean
  :default    true
  :audit      :getter)
(s/def :metabase-enterprise.advanced-config.file.databases.config-file-spec/name
  string?)
(s/def :metabase-enterprise.advanced-config.file.databases.config-file-spec/engine
  string?)
(s/def :metabase-enterprise.advanced-config.file.databases.config-file-spec/details
  map?)
(s/def ::config-file-spec
  (s/keys :req-un [:metabase-enterprise.advanced-config.file.databases.config-file-spec/engine
                   :metabase-enterprise.advanced-config.file.databases.config-file-spec/name
                   :metabase-enterprise.advanced-config.file.databases.config-file-spec/details]))
(defmethod advanced-config.file.i/section-spec :databases
  [_section]
  (s/spec (s/* ::config-file-spec)))
(defn- init-from-config-file!
  [database]
  (if (contains? database :delete)
    ;; Databases can be managed as a service by us.  When the service is canceled, we need to delete any information
    ;; Metabase has about them, including any stored credentials.  This is a config file flag instead of a CLI command,
    ;; so we can ensure the database stays deleted even after restoring backups.
    (let [magic-request (format "DELETE_WITH_DEPENDENTS:%s" (:name database))]
      (log/info (u/format-color :blue "Deleting databases via the config file is an internal feature subject to breaking changes."))
      (when (not= magic-request (:delete database))
        (throw (ex-info (format "To delete database %s set `delete` to %s" (pr-str (:name database)) (pr-str magic-request))
                        {:database-name (:name database)})))
      (when-let [existing-database-id (t2/select-one-pk Database :engine (:engine database), :name (:name database))]
        (log/info (u/format-color :blue "Deleting Database %s %s" (:engine database) (pr-str (:name database))))
        (t2/delete! Database existing-database-id)))
    (do
      ;; assert that we are able to connect to this Database. Otherwise, throw an Exception.
      (driver.u/can-connect-with-details? (keyword (:engine database)) (:details database) :throw-exceptions)
      (if-let [existing-database-id (t2/select-one-pk Database :engine (:engine database), :name (:name database))]
        (do
          (log/info (u/format-color :blue "Updating Database %s %s" (:engine database) (pr-str (:name database))))
          (t2/update! Database existing-database-id database))
        (do
          (log/info (u/format-color :green "Creating new %s Database %s" (:engine database) (pr-str (:name database))))
          (let [db (first (t2/insert-returning-instances! Database database))]
            (if (config-from-file-sync-databases)
              (future
                ((requiring-resolve 'metabase.sync/sync-database!) db))
              (log/info "Sync on database creation when initializing from file is disabled. Skipping sync."))))))))
(defmethod advanced-config.file.i/initialize-section! :databases
  [_section-name databases]
  (doseq [database databases]
    (init-from-config-file! database)))