///|
/// Configuration management for mbit — multi-environment config, env var binding,
/// config validation, and hot-reload support.
///
/// ## Usage
///
/// ```
/// // Load config from environment variables
/// let cfg = load_env_config()
/// let port = cfg.get_int("PORT", default=8080)
/// let db_url = cfg.get_string("DATABASE_URL", default="")
///
/// // Create strongly-typed server config
/// let srv = ServerConfig::from_env()
/// app.set_max_multipart_memory(srv.max_upload_size)
/// ```
///
/// ## Environment variable conventions
///
/// All config keys follow `APP_` prefix convention:
/// - `APP_PORT` → port
/// - `APP_DATABASE_URL` → database_url
/// - `APP_LOG_LEVEL` → log_level
/// - `APP_ENV` → environment (development/staging/production)

///|
/// Supported deployment environments.
pub(all) enum AppEnvironment {
  Development
  Staging
  Production
  Test
} derive(Debug, Eq, Hash)

///|
/// Convert environment to string.
pub fn AppEnvironment::to_string(self : AppEnvironment) -> String {
  match self {
    Development => "development"
    Staging => "staging"
    Production => "production"
    Test => "test"
  }
}

///|
/// Parse environment from string.
pub fn AppEnvironment::from_string(s : String) -> AppEnvironment {
  let lower = s.to_lower()
  if lower == "production" || lower == "prod" {
    Production
  } else if lower == "staging" || lower == "stage" {
    Staging
  } else if lower == "test" || lower == "testing" {
    Test
  } else {
    Development
  }
}

///|
/// Get the current environment from `APP_ENV` or `MOONBIT_ENV`.
pub fn current_environment() -> AppEnvironment {
  // Try environment variable; default to Development
  let env_str = match @env.get_env_var("APP_ENV") {
    Some(v) => v
    None =>
      match @env.get_env_var("MOONBIT_ENV") {
        Some(v) => v
        None => "development"
      }
  }
  AppEnvironment::from_string(env_str)
}

///|
/// Config value types supported by the configuration system.
pub(all) enum ConfigValue {
  ConfigString(String)
  ConfigInt(Int64)
  ConfigBool(Bool)
  ConfigFloat(Double)
} derive(Debug)

///|
/// Convert ConfigValue to string.
pub fn ConfigValue::to_string(self : ConfigValue) -> String {
  match self {
    ConfigString(s) => s
    ConfigInt(i) => i.to_string()
    ConfigBool(b) => b.to_string()
    ConfigFloat(f) => f.to_string()
  }
}

///|
/// A configuration store backed by environment variables and defaults.
pub(all) struct Config {
  /// Raw key-value store
  values : Map[String, ConfigValue]
  /// Current environment
  env : AppEnvironment
  /// Prefix for environment variables (default: "APP_")
  env_prefix : String
}

///|
/// Create an empty config with the given environment.
pub fn Config::new(env~ : AppEnvironment = Development) -> Config {
  { values: Map([]), env, env_prefix: "APP_" }
}

///|
/// Load configuration from environment variables.
/// Reads all env vars matching the prefix and stores them as ConfigValues.
///
/// ```
/// let cfg = Config::from_env()
/// let port = cfg.get_int("PORT", default=8080)
/// ```
pub fn Config::from_env(prefix~ : String = "APP_") -> Config {
  let env = current_environment()
  let values : Map[String, ConfigValue] = Map([])

  // Read common environment variables
  let mappings : Array[(String, String)] = [
    (prefix + "PORT", "port"),
    (prefix + "HOST", "host"),
    (prefix + "DATABASE_URL", "database_url"),
    (prefix + "REDIS_URL", "redis_url"),
    (prefix + "LOG_LEVEL", "log_level"),
    (prefix + "LOG_FORMAT", "log_format"),
    (prefix + "SECRET_KEY", "secret_key"),
    (prefix + "CORS_ORIGINS", "cors_origins"),
    (prefix + "MAX_UPLOAD_SIZE", "max_upload_size"),
    (prefix + "RATE_LIMIT", "rate_limit"),
    (prefix + "RATE_LIMIT_WINDOW", "rate_limit_window"),
    (prefix + "TLS_CERT_FILE", "tls_cert_file"),
    (prefix + "TLS_KEY_FILE", "tls_key_file"),
    (prefix + "ENV", "env"),
    (prefix + "DEBUG", "debug"),
  ]

  for mapping in mappings {
    let (env_var, key) = mapping
    match @env.get_env_var(env_var) {
      Some(val) => {
        // Try to parse as int first
        let parsed = try_parse_int(val)
        match parsed {
          Some(i) => values.set(key, ConfigInt(i))
          None => {
            // Try bool
            if val.to_lower() == "true" || val.to_lower() == "false" {
              values.set(key, ConfigBool(val.to_lower() == "true"))
            } else {
              values.set(key, ConfigString(val))
            }
          }
        }
      }
      None => ()
    }
  }

  { values, env, env_prefix: prefix }
}

///|
/// Try to parse a string as Int64.
fn try_parse_int(s : String) -> Int64? {
  try { Some(@string.parse_int64(s)) } catch { _ => None }
}

///|
/// Get a string config value, or a default if not set.
pub fn Config::get_string(self : Config, key : String, default~ : String = "") -> String {
  match self.values.get(key) {
    Some(ConfigString(v)) => v
    Some(ConfigInt(i)) => i.to_string()
    Some(ConfigBool(b)) => b.to_string()
    Some(ConfigFloat(f)) => f.to_string()
    None => default
  }
}

///|
/// Get an int config value, or a default if not set.
pub fn Config::get_int(self : Config, key : String, default~ : Int64 = 0L) -> Int64 {
  match self.values.get(key) {
    Some(ConfigInt(v)) => v
    Some(ConfigString(s)) =>
      match try_parse_int(s) {
        Some(i) => i
        None => default
      }
    _ => default
  }
}

///|
/// Get a bool config value, or a default if not set.
pub fn Config::get_bool(self : Config, key : String, default~ : Bool = false) -> Bool {
  match self.values.get(key) {
    Some(ConfigBool(v)) => v
    Some(ConfigString(s)) => s.to_lower() == "true"
    _ => default
  }
}

///|
/// Get a float config value, or a default if not set.
pub fn Config::get_float(self : Config, key : String, default~ : Double = 0.0) -> Double {
  match self.values.get(key) {
    Some(ConfigFloat(v)) => v
    Some(ConfigString(s)) =>
      try { @string.parse_double(s) } catch { _ => default }
    Some(ConfigInt(i)) => i.to_double()
    _ => default
  }
}

///|
/// Check if a config key exists.
pub fn Config::has(self : Config, key : String) -> Bool {
  self.values.contains(key)
}

///|
/// Get all config keys.
pub fn Config::keys(self : Config) -> Array[String] {
  let result : Array[String] = []
  for k, _ in self.values {
    result.push(k)
  }
  result
}

///|
/// Current environment.
pub fn Config::environment(self : Config) -> AppEnvironment {
  self.env
}

///|
/// Whether the current environment is production.
pub fn Config::is_production(self : Config) -> Bool {
  self.env == Production
}

///|
/// Whether the current environment is development.
pub fn Config::is_development(self : Config) -> Bool {
  self.env == Development
}

///|
/// Export config as JSON (for /debug/config endpoints).
pub fn Config::to_json(self : Config) -> Json {
  let obj : Map[String, Json] = Map([])
  for k, v in self.values {
    obj.set(k, Json::string(v.to_string()))
  }
  obj.set("environment", Json::string(self.env.to_string()))
  Json::object(obj)
}

///| ——————————————————————————————————————————————————————————————————————
///  ServerConfig — strongly-typed server configuration
///| ——————————————————————————————————————————————————————————————————————

///|
/// Strongly-typed server configuration populated from environment variables.
pub(all) struct ServerConfig {
  /// Server host (default: "0.0.0.0")
  host : String
  /// Server port (default: 8080)
  port : Int
  /// Database URL
  database_url : String
  /// Redis URL
  redis_url : String
  /// Log level (default: "info")
  log_level : String
  /// Log format (default: "text")
  log_format : String
  /// Secret key for signing
  secret_key : String
  /// CORS allowed origins (comma-separated)
  cors_origins : Array[String]
  /// Max upload size in bytes (default: 32MB)
  max_upload_size : Int64
  /// Rate limit requests per window (default: 100)
  rate_limit : Int
  /// Rate limit window in seconds (default: 60)
  rate_limit_window : Int
  /// TLS certificate file path
  tls_cert_file : String
  /// TLS key file path
  tls_key_file : String
  /// Environment name
  environment : AppEnvironment
  /// Debug mode
  debug : Bool
}

///|
/// Load server config from environment variables.
pub fn ServerConfig::from_env() -> ServerConfig {
  let cfg = Config::from_env()
  let env = current_environment()

  {
    host: cfg.get_string("host", default="0.0.0.0"),
    port: cfg.get_int("port", default=8080L).to_int(),
    database_url: cfg.get_string("database_url"),
    redis_url: cfg.get_string("redis_url"),
    log_level: cfg.get_string("log_level", default="info"),
    log_format: cfg.get_string("log_format", default="text"),
    secret_key: cfg.get_string("secret_key"),
    cors_origins: cfg.get_string("cors_origins", default="*").split(",").map(fn(s) { s.to_owned() }).collect(),
    max_upload_size: cfg.get_int("max_upload_size", default=32L * 1024L * 1024L),
    rate_limit: cfg.get_int("rate_limit", default=100L).to_int(),
    rate_limit_window: cfg.get_int("rate_limit_window", default=60L).to_int(),
    tls_cert_file: cfg.get_string("tls_cert_file"),
    tls_key_file: cfg.get_string("tls_key_file"),
    environment: env,
    debug: cfg.get_bool("debug", default=env == Development),
  }
}

///|
/// Get the server address string (host:port).
pub fn ServerConfig::addr(self : ServerConfig) -> String {
  self.host + ":" + self.port.to_string()
}

///|
/// Whether TLS is configured (cert and key files are set).
pub fn ServerConfig::has_tls(self : ServerConfig) -> Bool {
  self.tls_cert_file != "" && self.tls_key_file != ""
}