use std::{collections::HashMap, error::Error, ops::AddAssign, path::PathBuf};

use clap::{Parser, Subcommand};
use etherparse::{NetSlice, SlicedPacket};
use pcap::Capture;

fn main() -> Result<(), Box<dyn Error>> {
	let Args { file, cmd } = Args::parse();
	let mut pcap = Capture::from_file(file)?;

	match cmd {
		Cmd::Links => {
			for record in pcap.list_datalinks()? {
				let name = record.get_name().unwrap_or_else(|_| "<unnamed>".into());
				let desc = record.get_description().unwrap_or_default();
				println!("link  {name}  {desc}")
			}
		}

		Cmd::Stats => {
			let mut total = 0;
			let mut total_ipv4 = 0;
			let mut errs = 0;
			let mut first_timestamp = None;
			let mut last_timestamp = None;
			let mut protocols = HashMap::new();

			while let Ok(packet) = pcap.next_packet() {
				total += 1;

				last_timestamp = Some(packet.header.ts);
				if first_timestamp.is_none() {
					first_timestamp = last_timestamp;
				}

				let Ok(packet) = SlicedPacket::from_ethernet(packet.data) else {
					errs += 1;
					continue;
				};

				if let Some(NetSlice::Ipv4(ipv4)) = packet.net {
					let protocol = ipv4.header().protocol();
					protocols.entry(protocol).or_insert(0).add_assign(1);
					total_ipv4 += 1;
				}
			}

			println!("Count:           {total: >14}");
			println!("Count IPv4:      {total_ipv4: >14}");
			let non_ipv4 = total - total_ipv4;
			println!("non-IPv4 count:  {non_ipv4: >14}");
			let first_timestamp = first_timestamp.map(tv_to_sec).unwrap_or_default();
			println!("First timestamp: {first_timestamp: >14.2}");
			let last_timestamp = last_timestamp.map(tv_to_sec).unwrap_or_default();
			println!("Last timestamp:  {last_timestamp: >14.2}");
			let avg_packet = (last_timestamp - first_timestamp) / total as f64;
			println!("Avg packet rate: {avg_packet: >14.4}");
			println!("Errors:          {errs: >14}");

			let mut protocols: Vec<_> = protocols.into_iter().collect();
			protocols.sort_by_key(|(_, count)| *count);
			println!("Main Protocols:");
			for (num, count) in protocols.into_iter().take(5) {
				println!("- {num:?} {count: >14}");
			}
		}
	}

	Ok(())
}

#[derive(Parser)]
struct Args {
	file: PathBuf,

	#[command(subcommand)]
	cmd: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
	Links,
	Stats,
}

fn tv_to_sec(tv: libc::timeval) -> f64 {
	let usec_per_sec = 1_000_000.;
	let usecs = tv.tv_usec as f64 + (tv.tv_sec as f64 * usec_per_sec);
	usecs / usec_per_sec
}
