use std::{error::Error, process::Stdio};

use chrono::Duration;
use tokio::{
    io::{AsyncBufReadExt, BufReader},
    process::Command,
    sync::mpsc::UnboundedSender,
};
use tracing::{debug, trace};

#[derive(Debug)]
pub struct RTTps {
    pub timestamp: u64,
    pub rtt: Duration,
}

impl RTTps {
    fn new() -> Self {
        RTTps {
            timestamp: 0,
            rtt: Duration::min_value(),
        }
    }

    fn parse(&mut self, line: &str) -> Option<RTTData> {
        debug!(?line);
        if line.contains("time=") {
            let start = line.find('[')?;
            let end = line.find(']')?;
            let timestamp_str = &line[start + 1..end];

            let start = line.find("time=")?;
            let end = line.find(" ms")?;
            let rtt_str = &line[start + 5..end];

            let timestamp = timestamp_str.split('.').next()?.parse::<u64>().ok()?;
            let rtt_mus = rtt_str.parse::<f64>().ok()? * 1000f64;
            let rtt = Duration::microseconds(rtt_mus.round() as i64);

            Some(RTTData { timestamp, rtt })
        } else {
            None
        }
    }
}

#[derive(Debug)]
pub struct RTTData {
    pub timestamp: u64,
    pub rtt: Duration,
}

pub async fn run_rtt_eval(
    sender: UnboundedSender<Option<RTTData>>,
    ping_target: String,
) -> Result<(), Box<dyn Error>> {
    let mut ping_child = Command::new("ping")
        .arg("-D")
        .arg("-i")
        .arg(".2")
        .arg(ping_target.trim())
        .stdout(Stdio::piped())
        .spawn()?;

    let mut rttps = RTTps::new();
    let ping_stdout = ping_child.stdout.take().unwrap();

    let ping_handler = tokio::spawn(async move {
        let mut reader = BufReader::new(ping_stdout).lines();
        while let Some(line) = reader.next_line().await.unwrap() {
            let data = rttps.parse(&line);
            trace! {"{:?}", data}
            sender.send(data).expect("Couldn't send RTTData");
        }
    });

    ping_handler.await.unwrap();

    Ok(())
}
