///|
const MOONGATE_DB_BACKUP_DIR = "backups"

///|
fn moon_suite_app_types() -> Array[String] {
  [
    "claude", "claude-desktop", "codex", "gemini", "opencode", "openclaw", "hermes",
  ]
}

///|
fn moon_db_backup_dir() -> String {
  @pathx.join(moon_app_config_dir(), MOONGATE_DB_BACKUP_DIR)
}

///|
fn moon_db_backup_path(filename : String) -> String {
  @pathx.join(moon_db_backup_dir(), moon_backup_filename(filename))
}

///|
fn moon_backup_filename(filename : String) -> String {
  let sanitized = filename
    .replace(old="/", new="-")
    .replace(old="\\", new="-")
    .replace(old=":", new="-")
  if sanitized.has_suffix(".db") || sanitized.has_suffix(".json") {
    sanitized
  } else {
    sanitized + ".db"
  }
}

///|
fn moon_valid_db_backup_filename(filename : String) -> Bool {
  !filename.contains("..") &&
  !filename.contains("/") &&
  !filename.contains("\\") &&
  filename.has_suffix(".db")
}

///|
fn moon_db_backup_filename_error(filename : String) -> String? {
  if moon_valid_db_backup_filename(filename) {
    None
  } else {
    Some("Invalid backup filename")
  }
}

///|
fn moon_db_backup_new_filename(new_name : String) -> Result[String, String] {
  let trimmed = new_name.trim().to_owned()
  if trimmed.is_empty() {
    return Err("New name cannot be empty")
  }
  let name_part = if trimmed.has_suffix(".db") {
    trimmed[:trimmed.length() - 3].to_owned()
  } else {
    trimmed
  }
  if name_part.length() > 100 {
    return Err("Name too long (max 100 characters)")
  }
  if name_part.contains("..") ||
    name_part.contains("/") ||
    name_part.contains("\\") ||
    name_part.contains("\u{0000}") {
    return Err("Invalid characters in new name")
  }
  Ok(name_part + ".db")
}

///|
async fn moon_ensure_db_backup_dir() -> Unit {
  @fsx.make_directory(moon_db_backup_dir(), recursive=true, exists_ok=true) catch {
    _ => ()
  }
}

///|
async fn ConfigService::moon_export_snapshot_json(
  self : ConfigService,
  proxy : ProxyService,
  host~ : String,
  port~ : Int,
) -> Json {
  {
    "format": Json::string("moongate.config.v1"),
    "exportedAt": Json::number(@os.unix_seconds().to_double()),
    "settings": self.app_settings,
    "skills": moon_skill_state_load(),
    "proxy": proxy.moon_export_proxy_json(),
    "providers": proxy.moon_export_providers_json(),
    "suite": moongate_suite_manifest_json(host~, port~),
  }.to_json()
}

///|
fn ProxyService::moon_export_proxy_json(self : ProxyService) -> Json {
  {
    "globalConfig": self.global_config_json(),
    "takeoverStatus": self.takeover_status_json(),
    "appConfigs": self.moon_export_proxy_app_configs_json(),
    "defaultCostMultipliers": moon_string_map_json(
      self.proxy_default_cost_multipliers,
    ),
    "pricingModelSources": moon_string_map_json(
      self.proxy_pricing_model_sources,
    ),
    "upstreamProxyUrl": match self.upstream_proxy_url {
      Some(url) => Json::string(url)
      None => Json::null()
    },
  }.to_json()
}

///|
fn ProxyService::moon_export_proxy_app_configs_json(
  self : ProxyService,
) -> Json {
  let obj : Map[String, Json] = Map([], capacity=0)
  for app in moon_suite_app_types() {
    obj[app] = self.app_config_json(app)
  }
  Json::object(obj)
}

///|
fn ProxyService::moon_export_providers_json(
  self : ProxyService,
  include_secrets? : Bool = true,
) -> Json {
  let obj : Map[String, Json] = Map([], capacity=0)
  for app in moon_suite_app_types() {
    obj[app] = self.provider_router.providers_moon_json(app, include_secrets~)
  }
  obj["universalProviders"] = self.provider_router.universal_providers_json()
  obj["routeStats"] = self.provider_router.route_stats_json()
  obj["providerHealth"] = self.provider_router.provider_health_store_json()
  obj["circuitBreakerConfig"] = self.provider_router.circuit_config.to_json()
  obj["customEndpoints"] = self.provider_router.all_custom_endpoints_json()
  obj["routingPolicies"] = self.provider_router.routing_policy_store_json()
  let auto_failover : Map[String, Json] = {}
  for app in moon_suite_app_types() {
    auto_failover[app] = Json::boolean(
      self.provider_router.auto_failover_enabled_for_app(app),
    )
  }
  obj["autoFailover"] = Json::object(auto_failover)
  Json::object(obj)
}

///|
fn moon_string_map_json(map : Map[String, String]) -> Json {
  let obj : Map[String, Json] = Map([], capacity=0)
  for key in map.keys() {
    guard map.get(key) is Some(value) else { continue }
    obj[key] = Json::string(value)
  }
  Json::object(obj)
}

///|
async fn ConfigService::moon_export_config_to_file_json(
  self : ConfigService,
  proxy : ProxyService,
  host~ : String,
  port~ : Int,
  file_path : String,
) -> Json {
  let snapshot = self.moon_export_snapshot_json(proxy, host~, port~)
  @fsx.write_to_file(file_path, snapshot.stringify(indent=2) + "\n") catch {
    error =>
      return {
        "success": Json::boolean(false),
        "message": Json::string("Export failed: \{error}"),
        "filePath": Json::string(file_path),
      }.to_json()
  }
  {
    "success": Json::boolean(true),
    "message": Json::string("Configuration exported successfully"),
    "filePath": Json::string(file_path),
  }.to_json()
}

///|
async fn ConfigService::moon_import_config_from_file_json(
  self : ConfigService,
  proxy : ProxyService,
  host~ : String,
  port~ : Int,
  file_path : String,
) -> Json {
  let content = @fsx.read_file(file_path) catch {
    error =>
      return {
        "success": Json::boolean(false),
        "message": Json::string("Import failed: \{error}"),
        "filePath": Json::string(file_path),
      }.to_json()
  }
  let parsed = @json.parse(content) catch {
    error =>
      return {
        "success": Json::boolean(false),
        "message": Json::string("Import failed: \{error}"),
        "filePath": Json::string(file_path),
      }.to_json()
  }
  guard parsed is Object(snapshot) else {
    return {
      "success": Json::boolean(false),
      "message": Json::string(
        "Import failed: configuration root must be an object",
      ),
      "filePath": Json::string(file_path),
    }.to_json()
  }
  match snapshot.get("format") {
    Some(String("moongate.config.v1")) | None => ()
    _ =>
      return {
        "success": Json::boolean(false),
        "message": Json::string(
          "Import failed: unsupported configuration format",
        ),
        "filePath": Json::string(file_path),
      }.to_json()
  }
  match snapshot.get("providers") {
    Some(providers) =>
      match proxy.moon_apply_import_providers(providers, validate_only=true) {
        Ok(_) => ()
        Err(message) =>
          return {
            "success": Json::boolean(false),
            "message": Json::string("Import failed: \{message}"),
            "filePath": Json::string(file_path),
          }.to_json()
      }
    None => ()
  }
  let backup_id = self.moon_create_db_backup_json(proxy, host~, port~)
  self.moon_apply_import_snapshot(proxy, parsed)
  self.save_settings_store(self.app_settings)
  proxy.save_provider_store()
  proxy.save_proxy_store()
  {
    "success": Json::boolean(true),
    "message": Json::string("Configuration imported successfully"),
    "filePath": Json::string(file_path),
    "backupId": backup_id,
    "warning": Json::null(),
  }.to_json()
}

///|
async fn ConfigService::moon_apply_import_snapshot(
  self : ConfigService,
  proxy : ProxyService,
  snapshot : Json,
) -> Unit {
  guard snapshot is Object(obj) else { return }
  match obj.get("settings") {
    Some(settings) =>
      self.app_settings = moon_merge_settings_for_save(
        settings,
        self.app_settings,
      )
    None => ()
  }
  match obj.get("skills") {
    Some(skills) => ignore(moon_skill_state_save(skills))
    None => ()
  }
  match obj.get("proxy") {
    Some(Object(proxy_json)) => proxy.moon_apply_import_proxy(proxy_json)
    _ => ()
  }
  match obj.get("providers") {
    Some(providers) => ignore(proxy.moon_apply_import_providers(providers))
    None => ()
  }
}

///|
fn ProxyService::moon_apply_import_proxy(
  self : ProxyService,
  proxy : Map[String, Json],
) -> Unit {
  match proxy.get("globalConfig") {
    Some(Object(global)) => {
      match json_bool_from_map(global, "proxyEnabled") {
        Some(enabled) => self.proxy_enabled = enabled
        None => ()
      }
      match json_string_from_map(global, "listenAddress") {
        Some(address) => self.proxy_listen_address = address
        None => ()
      }
      match proxy_json_int(global, "listenPort") {
        Some(port) => self.proxy_listen_port = port
        None => ()
      }
      match json_bool_from_map(global, "enableLogging") {
        Some(enabled) => self.proxy_enable_logging = enabled
        None => ()
      }
    }
    _ => ()
  }
  match proxy.get("takeoverStatus") {
    Some(Object(takeover)) =>
      for app in moon_suite_app_types() {
        match json_bool_from_map(takeover, app) {
          Some(enabled) => self.proxy_takeover_enabled.set(app, enabled)
          None => ()
        }
      }
    _ => ()
  }
  match proxy.get("appConfigs") {
    Some(Object(app_configs)) =>
      for app in moon_suite_app_types() {
        guard app_configs.get(app) is Some(Object(config)) else { continue }
        match json_bool_from_map(config, "enabled") {
          Some(enabled) => self.proxy_app_enabled.set(app, enabled)
          None => ()
        }
        match json_bool_from_map(config, "autoFailoverEnabled") {
          Some(enabled) => self.provider_router.set_auto_failover(app, enabled)
          None => ()
        }
        match proxy_json_int(config, "maxRetries") {
          Some(value) => self.provider_router.set_max_retries(app, value)
          None => ()
        }
        match proxy_json_int(config, "streamingFirstByteTimeout") {
          Some(value) => self.proxy_streaming_first_byte_timeout.set(app, value)
          None => ()
        }
        match proxy_json_int(config, "streamingIdleTimeout") {
          Some(value) => self.proxy_streaming_idle_timeout.set(app, value)
          None => ()
        }
        match proxy_json_int(config, "nonStreamingTimeout") {
          Some(value) => self.proxy_non_streaming_timeout.set(app, value)
          None => ()
        }
        match
          proxy_circuit_config_from_request(
            "",
            Some(Json::object(config)),
            self.provider_router.circuit_config_for_app(app),
          ) {
          Some(parsed) => self.provider_router.update_app_configs(app, parsed)
          None => ()
        }
      }
    _ => ()
  }
  self.proxy_default_cost_multipliers.clear()
  match proxy.get("defaultCostMultipliers") {
    Some(Object(values)) =>
      for app, value in values {
        if value is String(text) {
          self.proxy_default_cost_multipliers.set(app, text)
        }
      }
    _ => ()
  }
  self.proxy_pricing_model_sources.clear()
  match proxy.get("pricingModelSources") {
    Some(Object(values)) =>
      for app, value in values {
        if value is String(text) {
          self.proxy_pricing_model_sources.set(app, text)
        }
      }
    _ => ()
  }
  match proxy.get("upstreamProxyUrl") {
    Some(String(url)) if upstream_proxy_url_is_valid(url) =>
      self.upstream_proxy_url = Some(url)
    Some(Null) => self.upstream_proxy_url = None
    _ => ()
  }
}

///|
async fn ConfigService::moon_create_db_backup_json(
  self : ConfigService,
  proxy : ProxyService,
  host~ : String,
  port~ : Int,
) -> Json {
  moon_ensure_db_backup_dir()
  let filename = moon_new_backup_filename()
  let path = moon_db_backup_path(filename)
  let snapshot = self.moon_export_snapshot_json(proxy, host~, port~)
  @fsx.write_to_file(path, snapshot.stringify(indent=2) + "\n") catch {
    _ => return Json::string("")
  }
  Json::string(filename)
}

///|
fn moon_new_backup_filename() -> String {
  "moongate-\{@os.unix_seconds()}-\{moongate_random_id()}.db"
}

///|
async fn moon_list_db_backups_json() -> Json {
  let entries = @fsx.list_directory(moon_db_backup_dir()) catch {
    _ => return Json::array([])
  }
  let rows : Array[MoonDbBackupEntry] = []
  for entry in entries {
    if !(entry.kind is Regular) || !entry.name.has_suffix(".db") {
      continue
    }
    let content = @fsx.read_file(entry.path) catch { _ => "" }
    let modified_at = moon_file_mtime(entry.path)
    rows.push({
      filename: entry.name,
      size_bytes: content.length(),
      created_at: modified_at,
    })
  }
  moon_sort_db_backup_entries(rows)
  Json::array(rows.map(fn(row) { row.to_json() }))
}

///|
async fn ConfigService::moon_restore_db_backup_json(
  self : ConfigService,
  proxy : ProxyService,
  filename : String,
) -> Result[Json, String] {
  match moon_db_backup_filename_error(filename) {
    Some(error) => return Err(error)
    None => ()
  }
  let path = moon_db_backup_path(filename)
  if !(@fsx.exists_as_file(path) catch { _ => false }) {
    return Err("Backup file not found: \{filename}")
  }
  let content = @fsx.read_file(path) catch { error => return Err("\{error}") }
  let parsed = @json.parse(content) catch { error => return Err("\{error}") }
  self.moon_apply_import_snapshot(proxy, parsed)
  self.save_settings_store(self.app_settings) catch {
    error => return Err("Failed to persist restored settings: \{error}")
  }
  proxy.save_provider_store() catch {
    error => return Err("Failed to persist restored providers: \{error}")
  }
  proxy.save_proxy_store() catch {
    error => return Err("Failed to persist restored proxy settings: \{error}")
  }
  Ok(Json::string(filename))
}

///|
async fn moon_rename_db_backup_json(
  old_filename : String,
  new_name : String,
) -> Result[Json, String] {
  match moon_db_backup_filename_error(old_filename) {
    Some(error) => return Err(error)
    None => ()
  }
  let new_filename = match moon_db_backup_new_filename(new_name) {
    Ok(name) => name
    Err(error) => return Err(error)
  }
  let old_path = moon_db_backup_path(old_filename)
  let new_path = moon_db_backup_path(new_filename)
  if !(@fsx.exists_as_file(old_path) catch { _ => false }) {
    return Err("Backup file not found: \{old_filename}")
  }
  let new_path_exists = @fsx.exists_as_file(new_path) catch { _ => false }
  if new_path_exists {
    return Err("A backup named '\{new_filename}' already exists")
  }
  let content = @fsx.read_file(old_path) catch {
    error => return Err("\{error}")
  }
  moon_ensure_db_backup_dir()
  @fsx.write_to_file(new_path, content) catch {
    error => return Err("\{error}")
  }
  @fsx.remove(old_path) catch {
    _ => ()
  }
  Ok(Json::string(new_filename))
}

///|
async fn moon_delete_db_backup_json(filename : String) -> Result[Json, String] {
  match moon_db_backup_filename_error(filename) {
    Some(error) => return Err(error)
    None => ()
  }
  let path = moon_db_backup_path(filename)
  if !(@fsx.exists_as_file(path) catch { _ => false }) {
    return Err("Backup file not found: \{filename}")
  }
  @fsx.remove(path) catch {
    error => return Err("\{error}")
  }
  Ok(Json::null())
}

///|
priv struct MoonDbBackupEntry {
  filename : String
  size_bytes : Int
  created_at : Int64
}

///|
fn moon_sort_db_backup_entries(rows : Array[MoonDbBackupEntry]) -> Unit {
  rows.sort_by(fn(left, right) { right.created_at.compare(left.created_at) })
}

///|
fn MoonDbBackupEntry::to_json(self : MoonDbBackupEntry) -> Json {
  {
    "filename": Json::string(self.filename),
    "sizeBytes": Json::number(self.size_bytes.to_double()),
    "createdAt": Json::string(self.created_at.to_string()),
  }.to_json()
}