speedtest-tui 0.1.1

A terminal-based network speed test tool with real-time gauges and graphs
use anyhow::Result;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ConnectionInfo {
    pub ip: String,
    pub isp: Option<String>,
    pub location: Option<String>,
    pub country: Option<String>,
    pub city: Option<String>,
    pub region: Option<String>,
    pub timezone: Option<String>,
}

#[derive(Debug, Deserialize)]
struct IpInfoResponse {
    ip: String,
    org: Option<String>,
    city: Option<String>,
    region: Option<String>,
    country: Option<String>,
    timezone: Option<String>,
}

/// Get connection information (public IP, ISP, location)
pub async fn get_connection_info() -> Result<ConnectionInfo> {
    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(10))
        .build()?;

    // Try ipinfo.io first (free tier: 50k requests/month)
    let response = client
        .get("https://ipinfo.io/json")
        .header("Accept", "application/json")
        .send()
        .await?;

    if response.status().is_success() {
        let info: IpInfoResponse = response.json().await?;

        let location = match (&info.city, &info.region, &info.country) {
            (Some(city), Some(region), Some(country)) => {
                Some(format!("{}, {}, {}", city, region, country))
            }
            (Some(city), _, Some(country)) => Some(format!("{}, {}", city, country)),
            (_, _, Some(country)) => Some(country.clone()),
            _ => None,
        };

        return Ok(ConnectionInfo {
            ip: info.ip,
            isp: info.org,
            location,
            country: info.country,
            city: info.city,
            region: info.region,
            timezone: info.timezone,
        });
    }

    // Fallback to simple IP lookup
    let response = client
        .get("https://api.ipify.org?format=json")
        .send()
        .await?;

    #[derive(Deserialize)]
    struct IpifyResponse {
        ip: String,
    }

    let info: IpifyResponse = response.json().await?;

    Ok(ConnectionInfo {
        ip: info.ip,
        ..Default::default()
    })
}

#[allow(dead_code)]
pub fn get_local_ip() -> Result<String> {
    let ip = local_ip_address::local_ip()?;
    Ok(ip.to_string())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_get_connection_info() {
        let result = get_connection_info().await;
        assert!(result.is_ok());
        let info = result.unwrap();
        assert!(!info.ip.is_empty());
    }

    #[test]
    fn test_get_local_ip() {
        let result = get_local_ip();
        assert!(result.is_ok());
    }
}