use std::{io::Error, path::Path, process::Stdio, time::Duration};

use byte_unit::{AdjustedByte, Byte};
use tokio::{
    io::{AsyncBufReadExt, BufReader},
    process::Command,
    sync::mpsc::UnboundedSender,
};
use tracing::{debug, trace};

struct Bandwidth {
    time: Duration,
    data: Byte,
}

#[derive(Default, Debug)]
struct Bps {
    /// Last recorded time
    last_time: Duration,
    /// The current total time
    total_time: Duration,
    /// The current total length
    total_len: Byte,
}

impl Bps {
    fn new() -> Self {
        Self::default()
    }

    fn update(&mut self, data: &str) -> Option<BpsData> {
        let x: Vec<&str> = data.trim().split('\t').collect();
        let epoch = Duration::from_secs_f64(x[0].parse::<f64>().unwrap_or_default());
        let _src = x[1];
        let _dst = x[2];
        let len = Byte::from_bytes((x[3].parse::<usize>().unwrap_or_default() as u64).into());
        let mut res = None;

        if self.total_time > Duration::from_secs(1) {
            // One second elapsed
            let window_size = self.total_len; // Total amount of bytes
            let window_time = epoch; // Duration of the window

            let bandwidth = Bandwidth {
                time: window_time,
                data: window_size,
            };

            self.reset(epoch);

            res = Some(BpsData {
                timestamp: bandwidth.time.as_secs(),
                data: bandwidth.data.get_appropriate_unit(false),
            });
        } else {
            // We're still in the window
            // One second hasn't elapsed yet
            // Difference between current time and last time a pkt got recorded
            let delta = if epoch > self.last_time {
                epoch - self.last_time
            } else {
                self.last_time - epoch
            };

            self.last_time = epoch;
            self.total_time += delta;
            self.total_len = Byte::from_bytes(self.total_len.get_bytes() + len.get_bytes());
        }
        trace!("Bps: {:?}", self);

        res
    }

    fn reset(&mut self, last_time: Duration) {
        self.last_time = last_time;
        self.total_time = Duration::default();
        self.total_len = Byte::default();
    }
}

#[derive(Debug)]
pub struct BpsData {
    pub timestamp: u64,
    pub data: AdjustedByte,
}

pub async fn run_bandwidth_eval(
    pcap_file: &Path,
    sender: UnboundedSender<Option<BpsData>>,
) -> Result<(), Error> {
    debug!("Running tail...");
    let mut tail_child = Command::new("tail")
        .args(["-f", "-c", "+0", pcap_file.as_os_str().to_str().unwrap()])
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let tail_stdout: Stdio = tail_child.stdout.take().unwrap().try_into().unwrap();

    debug!("Running tshark...");
    let mut tshark_child = Command::new("tshark")
        .args([
            "-T",
            "fields",
            "-e",
            "frame.time_epoch",
            "-e",
            "ip.src_host",
            "-e",
            "ip.dst_host",
            "-e",
            "frame.len",
            "-i",
            "-",
        ])
        .stdin(tail_stdout)
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn()?;

    let mut bps = Bps::new();
    let tshark_stdout = tshark_child.stdout.take().unwrap();

    let tshark_handler = tokio::spawn(async move {
        let mut reader = BufReader::new(tshark_stdout).lines();
        let mut counter = 0;
        while let Some(line) = reader.next_line().await.unwrap() {
            trace!("Pkt {}: {}", counter, &line);
            counter += 1;
            let data = bps.update(&line);
            sender.send(data).expect("Couldn't send BpsData");
        }
    });

    tshark_handler.await.unwrap();

    Ok(())
}
