;; modules/load_config.fnl
;; Loads, validates, and provides the configuration values in 'config.fnl'

;; -------------------------------------------------------------------------- ;;
;; Load user configuration file

(var config nil)
(let [(ok err) (pcall #(set config (require :config)))]
  (when (not ok)
    (print "Stopping server: Missing or invalid config file. See `config.fnl.example` for an example config file.")
    (print err)
    (os.exit)))

;; -------------------------------------------------------------------------- ;;
;; Expected configuration structure

(local expected_config_structure {:auth_secret {:type "string"
                                                :default "example auth secret"
                                                :not_default true}
                                  :landing_page_index {:type "string"
                                                       :default "index.html"}
                                  :listen_port {:type "number"
                                                :default 80}
                                  :provide_api? {:type "boolean"
                                                 :default true}
                                  :provide_control_panel? {:type "boolean"
                                                           :default true
                                                           :depends_on {:provide_api? true}}
                                  :provide_landing_page? {:type "boolean"
                                                          :default true}
                                  :query_limit {:type "number"
                                                :default 100}
                                  :session_max_length_hours {:type "number"
                                                             :default 24}
                                  :session_max_idle_length_hours {:type "number"
                                                                  :default 1}
                                  :use_secure_cookies? {:type "boolean"
                                                        :default false}
                                  :use_totp? {:type "boolean"
                                              :default false}})

;; -------------------------------------------------------------------------- ;;
;; Sanity checks

;; ---------------------------------- ;;
;; Ensure that the configured values are of the appropriate types
;; (This also checks for missing values: they will be reported as being of type "nil")

(fn assert_config_value_type [key type_str]
  "Assert that the value at `key` in the config file is of type `type_str`."
  (when (not (= (type (. config key)) type_str))
    (print "Stopping server: Invalid value type in your config file.")
    (print " -      Key name: " key)
    (print " - Expected type: " type_str)
    (print " -   Actual type: " (type (. config key)))
    (os.exit)))

(each [setting_key setting_properties (pairs expected_config_structure)]
  (when (?. setting_properties :type)
    (assert_config_value_type setting_key setting_properties.type)))

;; ---------------------------------- ;;
;; Ensure that the configured values have their dependencies satisfied

(fn assert_config_value_dependencies_satisfied [key dependencies]
  "Assert that the value at `key` in the config file has its dependencies `dependencies` satisfied.
`dependencies` is a table of config file keys and their respective expected values."
  (each [dependency_key dependency_value (pairs dependencies)]
    (when (not (= (. config dependency_key) dependency_value))
      (print "Stopping server: Invalid configuration in your config file.")
      (print " -    Key name: " key)
      (print " -  Depends on: " dependency_key dependency_value)
      (print " - Your config: " dependency_key (. config dependency_key))
      (os.exit))))

(each [setting_key setting_properties (pairs expected_config_structure)]
  (when (?. setting_properties :depends_on)
    (assert_config_value_dependencies_satisfied setting_key setting_properties.depends_on)))

;; ---------------------------------- ;;
;; Ensure that defaults aren't used for security-critical settings

(fn assert_config_value_not_default [key default]
  "Assert that the value at `key` in the config file has been changed from its default value of `default`."
  (when (= (. config key) default)
    (print "Stopping server: Do not use the default values for security-critical settings in your config file!")
    (print " -      Key name: " key )
    (print " - Default value: " default)
    (print " -   Found value: " (. config key))
    (os.exit)))

(each [setting_key setting_properties (pairs expected_config_structure)]
  (when (?. setting_properties :not_default)
    (assert_config_value_not_default setting_key setting_properties.not_default)))

;; -------------------------------------------------------------------------- ;;
;; Return loaded config

config
