use crate::config::Config;
use crate::output::{self, error, print_kv, success};
use anyhow::{Context, Result};
pub async fn config_show(format: String) -> Result<()> {
let config = Config::load()?;
if format == "json" {
let json = serde_json::to_string_pretty(&config)?;
println!("{}", json);
} else {
println!("IPFRS Configuration");
println!("===================");
println!();
println!("General:");
println!("--------");
print_kv(
" Data directory",
&config.general.data_dir.display().to_string(),
);
print_kv(" Log level", &config.general.log_level);
print_kv(" Color output", &config.general.color.to_string());
print_kv(" Default format", &config.general.format);
println!();
println!("Storage:");
println!("--------");
print_kv(" Blocks path", &config.storage.blocks_path);
print_kv(" Cache size", &format_bytes(config.storage.cache_size));
print_kv(" WAL enabled", &config.storage.wal_enabled.to_string());
print_kv(" GC interval", &format!("{}s", config.storage.gc_interval));
println!();
println!("Network:");
println!("--------");
print_kv(
" Max connections",
&config.network.max_connections.to_string(),
);
println!(" Listen addresses:");
for addr in &config.network.listen_addrs {
println!(" - {}", addr);
}
println!();
println!("Gateway:");
println!("--------");
print_kv(" Listen address", &config.gateway.listen_addr);
println!();
println!("API:");
println!("----");
print_kv(" Listen address", &config.api.listen_addr);
print_kv(" Auth enabled", &config.api.auth_enabled.to_string());
print_kv(" Timeout", &format!("{}s", config.api.timeout));
if let Some(ref remote_url) = config.api.remote_url {
print_kv(" Remote URL", remote_url);
}
println!();
}
Ok(())
}
pub async fn config_export(output: String, format: String) -> Result<()> {
let config = Config::load()?;
let content = if format == "json" {
serde_json::to_string_pretty(&config)?
} else if format == "toml" {
toml::to_string_pretty(&config)?
} else if format == "yaml" {
serde_yaml::to_string(&config)?
} else {
anyhow::bail!(
"Unsupported export format: {}. Use json, toml, or yaml.",
format
);
};
let content_len = content.len();
std::fs::write(&output, content)
.with_context(|| format!("Failed to write configuration to {}", output))?;
success(&format!("Configuration exported to {}", output));
print_kv("Format", &format);
print_kv("Size", &format!("{} bytes", content_len));
Ok(())
}
pub async fn config_import(input: String, dry_run: bool) -> Result<()> {
let content = std::fs::read_to_string(&input)
.with_context(|| format!("Failed to read configuration from {}", input))?;
let format = if input.ends_with(".json") {
"json"
} else if input.ends_with(".toml") {
"toml"
} else if input.ends_with(".yaml") || input.ends_with(".yml") {
"yaml"
} else {
if content.trim().starts_with('{') {
"json"
} else if content.contains('[') && content.contains(']') {
"toml"
} else {
"yaml"
}
};
let config: Config = match format {
"json" => serde_json::from_str(&content)?,
"toml" => toml::from_str(&content)?,
"yaml" => serde_yaml::from_str(&content)?,
_ => anyhow::bail!("Could not detect configuration format"),
};
if dry_run {
output::info("Dry run mode - configuration validation only");
success("✓ Configuration is valid");
print_kv("Format detected", format);
println!();
println!("Configuration summary:");
print_kv(
" Data directory",
&config.general.data_dir.display().to_string(),
);
print_kv(" Log level", &config.general.log_level);
print_kv(
" Max connections",
&config.network.max_connections.to_string(),
);
println!();
output::info("Use without --dry-run to apply the configuration");
} else {
let config_path = Config::default_path()?;
if config_path.exists() {
let backup_path = config_path.with_extension("toml.backup");
std::fs::copy(&config_path, &backup_path)?;
output::info(&format!("Backed up existing config to {:?}", backup_path));
}
let toml_content = toml::to_string_pretty(&config)?;
std::fs::write(&config_path, toml_content)?;
success("Configuration imported successfully");
print_kv("Source", &input);
print_kv("Destination", &config_path.display().to_string());
print_kv("Format", format);
println!();
output::info("Restart the daemon to apply changes: ipfrs daemon restart");
}
Ok(())
}
pub async fn config_edit() -> Result<()> {
let config_path = Config::default_path()?;
if !config_path.exists() {
error("Configuration file does not exist");
println!();
output::info("Initialize a new configuration:");
println!(" ipfrs init");
return Ok(());
}
let editor = std::env::var("EDITOR")
.or_else(|_| std::env::var("VISUAL"))
.unwrap_or_else(|_| "vi".to_string());
println!("Opening configuration in {}...", editor);
print_kv("Config file", &config_path.display().to_string());
println!();
let status = std::process::Command::new(&editor)
.arg(&config_path)
.status()
.with_context(|| format!("Failed to open editor: {}", editor))?;
if status.success() {
success("Configuration editor closed");
println!();
output::info("Restart the daemon to apply changes: ipfrs daemon restart");
} else {
error("Editor exited with error");
}
Ok(())
}
fn format_bytes(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} bytes", bytes)
}
}