use super::types::RateLimitStatus;
use chrono::{DateTime, Duration, Utc};
pub async fn check_rate_limit_status() -> Option<RateLimitStatus> {
let blocked_panes = super::tmux::scan_for_blocked_panes(100);
if blocked_panes.is_empty() {
Some(RateLimitStatus {
is_limited: false,
last_checked_at: Utc::now(),
five_hour_resets_at: None,
weekly_resets_at: None,
next_reset_at: None,
})
} else {
let next_reset = Utc::now() + Duration::minutes(30);
Some(RateLimitStatus {
is_limited: true,
last_checked_at: Utc::now(),
five_hour_resets_at: Some(next_reset),
weekly_resets_at: None,
next_reset_at: Some(next_reset),
})
}
}
pub fn format_time_until_reset(reset_at: DateTime<Utc>) -> String {
let now = Utc::now();
let duration = reset_at.signed_duration_since(now);
if duration.num_seconds() < 0 {
return "Reset time has passed".to_string();
}
let hours = duration.num_hours();
let minutes = duration.num_minutes() % 60;
let seconds = duration.num_seconds() % 60;
if hours > 0 {
format!("{}h {}m {}s", hours, minutes, seconds)
} else if minutes > 0 {
format!("{}m {}s", minutes, seconds)
} else {
format!("{}s", seconds)
}
}
pub fn format_rate_limit_status(status: &RateLimitStatus) -> String {
if !status.is_limited {
return "No active rate limits".to_string();
}
let mut parts = vec!["Rate limit active".to_string()];
if let Some(reset_at) = status.next_reset_at {
let time_until = format_time_until_reset(reset_at);
parts.push(format!("Resets in: {}", time_until));
parts.push(format!(
"Reset time: {}",
reset_at.format("%Y-%m-%d %H:%M:%S UTC")
));
}
if let Some(five_hour_reset) = status.five_hour_resets_at {
parts.push(format!(
"5-hour window resets: {} ({})",
five_hour_reset.format("%H:%M:%S UTC"),
format_time_until_reset(five_hour_reset)
));
}
if let Some(weekly_reset) = status.weekly_resets_at {
parts.push(format!(
"Weekly limit resets: {} ({})",
weekly_reset.format("%Y-%m-%d %H:%M UTC"),
format_time_until_reset(weekly_reset)
));
}
parts.join("\n")
}
pub fn parse_rate_limit_from_error(error_text: &str) -> Option<RateLimitStatus> {
let error_lower = error_text.to_lowercase();
if !error_lower.contains("rate limit")
&& !error_lower.contains("429")
&& !error_lower.contains("too many requests")
{
return None;
}
Some(RateLimitStatus {
is_limited: true,
last_checked_at: Utc::now(),
five_hour_resets_at: None,
weekly_resets_at: None,
next_reset_at: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::Duration;
#[test]
fn test_format_time_until_reset() {
let future =
Utc::now() + Duration::hours(2) + Duration::minutes(30) + Duration::seconds(45);
let formatted = format_time_until_reset(future);
assert!(formatted.contains("2h"));
assert!(formatted.contains("30m"));
}
#[test]
fn test_format_time_minutes_only() {
let future = Utc::now() + Duration::minutes(5) + Duration::seconds(30);
let formatted = format_time_until_reset(future);
assert!(formatted.contains("5m"));
assert!(!formatted.contains("h"));
}
#[test]
fn test_format_time_past() {
let past = Utc::now() - Duration::hours(1);
let formatted = format_time_until_reset(past);
assert!(formatted.contains("passed"));
}
#[test]
fn test_parse_rate_limit_from_error() {
let error = "Rate limit reached. Please try again later.";
let status = parse_rate_limit_from_error(error);
assert!(status.is_some());
assert!(status.unwrap().is_limited);
}
#[test]
fn test_parse_non_rate_limit_error() {
let error = "Network connection failed";
let status = parse_rate_limit_from_error(error);
assert!(status.is_none());
}
}