use crate::monitor::net_info::NetworkInfo;
use crate::monitor::{ConnectionStatus, NetworkStats};
use crate::ui::tui::TuiState;
pub fn format_summary(state: &TuiState, short: bool) -> String {
if short {
format_short_summary(state)
} else {
format_default_summary(state)
}
}
pub fn format_cli_summary(
stats: &NetworkStats,
network_info: Option<&NetworkInfo>,
duration_secs: i64,
short: bool,
) -> String {
if short {
format_short_cli_summary(stats, network_info, duration_secs)
} else {
format_default_cli_summary(stats, network_info, duration_secs)
}
}
fn get_status_symbol(status: ConnectionStatus) -> &'static str {
match status {
ConnectionStatus::Ok => "✓",
ConnectionStatus::Slow => "⚠",
ConnectionStatus::Disconnected => "✗",
}
}
fn format_duration(seconds: i64) -> String {
let hours = seconds / 3600;
let minutes = (seconds % 3600) / 60;
let secs = seconds % 60;
if hours > 0 {
format!("{}h {}m {}s", hours, minutes, secs)
} else if minutes > 0 {
format!("{}m {}s", minutes, secs)
} else {
format!("{}s", secs)
}
}
fn format_network_line(network_info: Option<&NetworkInfo>) -> String {
if let Some(info) = network_info {
let mut parts = Vec::new();
if let Some(ip) = info.public_ipv4.as_ref().or(info.public_ipv6.as_ref()) {
parts.push(ip.clone());
}
match (&info.isp, &info.asn) {
(Some(isp), Some(asn)) => parts.push(format!("{} ({})", isp, asn)),
(Some(isp), None) => parts.push(isp.clone()),
(None, Some(asn)) => parts.push(asn.clone()),
(None, None) => {}
}
match (&info.city, &info.country) {
(Some(city), Some(country)) => parts.push(format!("{}, {}", city, country)),
(Some(city), None) => parts.push(city.clone()),
(None, Some(country)) => parts.push(country.clone()),
(None, None) => {}
}
if parts.is_empty() {
"N/A".to_string()
} else {
parts.join(" • ")
}
} else {
"N/A".to_string()
}
}
fn format_default_summary(state: &TuiState) -> String {
if state.stats.is_none() && state.history.is_empty() {
return "Session Summary:\n No data collected".to_string();
}
let stats = state.stats.as_ref();
let status_symbol = if let Some(s) = stats {
get_status_symbol(s.status)
} else {
"✗"
};
let session_duration = chrono::Duration::seconds(state.running_time_seconds());
let (_avg_latency, uptime_pct) = state.get_window_stats(session_duration);
let disconnection_count = state.disconnections.len();
let total_downtime_secs: i64 = state
.disconnections
.iter()
.map(|d| d.duration_seconds())
.sum();
let running_time = state.format_running_time();
let availability_line = if disconnection_count == 0 {
format!(
" {} {:.1}% uptime (no outages) | {}",
status_symbol, uptime_pct, running_time
)
} else {
format!(
" {} {:.1}% uptime ({} {}, {} total downtime) | {}",
status_symbol,
uptime_pct,
disconnection_count,
if disconnection_count == 1 {
"outage"
} else {
"outages"
},
format_duration(total_downtime_secs),
running_time
)
};
let latency_ms = stats.map(|s| s.avg_latency_ms).unwrap_or(0.0);
let performance_line = format!(" {} {:.0}ms avg latency", status_symbol, latency_ms);
let network_line = format!(
" Network: {}",
format_network_line(state.network_info.as_ref())
);
format!(
"Session Summary:\n{}\n{}\n{}",
availability_line, performance_line, network_line
)
}
fn format_short_summary(state: &TuiState) -> String {
if state.stats.is_none() && state.history.is_empty() {
return "No data collected".to_string();
}
let stats = state.stats.as_ref();
let status_symbol = if let Some(s) = stats {
get_status_symbol(s.status)
} else {
"✗"
};
let session_duration = chrono::Duration::seconds(state.running_time_seconds());
let (_avg_latency, uptime_pct) = state.get_window_stats(session_duration);
let latency_ms = stats.map(|s| s.avg_latency_ms).unwrap_or(0.0);
let network_short = if let Some(info) = &state.network_info {
let mut parts = Vec::new();
if let Some(ip) = info.public_ipv4.as_ref().or(info.public_ipv6.as_ref()) {
parts.push(ip.clone());
}
if let Some(isp) = &info.isp {
parts.push(isp.clone());
}
if parts.is_empty() {
"N/A".to_string()
} else {
parts.join(" • ")
}
} else {
"N/A".to_string()
};
let running_time = state.format_running_time();
format!(
"{} {:.1}% uptime | {:.0}ms | {} | (test duration: {})",
status_symbol, uptime_pct, latency_ms, network_short, running_time
)
}
fn format_default_cli_summary(
stats: &NetworkStats,
network_info: Option<&NetworkInfo>,
duration_secs: i64,
) -> String {
let status_symbol = get_status_symbol(stats.status);
let uptime_pct = if stats.status == ConnectionStatus::Disconnected {
0.0
} else {
100.0
};
let availability_line = format!(
" {} {:.1}% uptime (no outages) | {}",
status_symbol,
uptime_pct,
format_duration(duration_secs)
);
let performance_line = format!(
" {} {:.0}ms avg latency",
status_symbol, stats.avg_latency_ms
);
let network_line = format!(" Network: {}", format_network_line(network_info));
format!(
"Session Summary:\n{}\n{}\n{}",
availability_line, performance_line, network_line
)
}
fn format_short_cli_summary(
stats: &NetworkStats,
network_info: Option<&NetworkInfo>,
duration_secs: i64,
) -> String {
let status_symbol = get_status_symbol(stats.status);
let uptime_pct = if stats.status == ConnectionStatus::Disconnected {
0.0
} else {
100.0
};
let network_short = if let Some(info) = network_info {
let mut parts = Vec::new();
if let Some(ip) = info.public_ipv4.as_ref().or(info.public_ipv6.as_ref()) {
parts.push(ip.clone());
}
if let Some(isp) = &info.isp {
parts.push(isp.clone());
}
if parts.is_empty() {
"N/A".to_string()
} else {
parts.join(" • ")
}
} else {
"N/A".to_string()
};
format!(
"{} {:.1}% uptime | {:.0}ms | {} | (test duration: {})",
status_symbol,
uptime_pct,
stats.avg_latency_ms,
network_short,
format_duration(duration_secs)
)
}