use std::{io::Read, path::Path};

use anyhow::Context;
use pcap_file::{pcap, pcapng};

use crate::monitor::Event;

pub enum Reader<R: Read> {
    Pcap(pcap::PcapReader<R>),
    PcapNg(pcapng::PcapNgReader<R>),
}

impl Reader<std::fs::File> {
    pub fn new(path: impl AsRef<Path>) -> anyhow::Result<Self> {
        let is_pcapng = path.as_ref().extension().is_some_and(|ext| ext == "pcapng");
        let file = std::fs::File::open(&path)
            .with_context(|| format!("Failed to open file at `{}`", path.as_ref().display()))?;

        if is_pcapng {
            let reader = pcapng::PcapNgReader::new(file)?;
            Ok(Reader::PcapNg(reader))
        } else {
            let reader = pcap::PcapReader::new(file)?;
            let datalink = reader.header().datalink;
            if datalink != pcap_file::DataLink::DBUS {
                anyhow::bail!("Invalid datalink type `{datalink:?}`");
            }
            Ok(Reader::Pcap(reader))
        }
    }
}

impl<R: Read> Iterator for Reader<R> {
    type Item = anyhow::Result<Event>;

    fn next(&mut self) -> Option<Self::Item> {
        match self {
            Reader::Pcap(inner) => match inner.next_packet() {
                Some(Ok(packet)) => Some(Event::from_bytes(
                    packet.data.to_vec(),
                    packet.timestamp.into(),
                )),
                Some(Err(err)) => Some(Err(err.into())),
                None => None,
            },
            Reader::PcapNg(inner) => match inner.next_block() {
                Some(Ok(pcapng::blocks::Block::InterfaceDescription(iface))) => {
                    if iface.linktype != pcap_file::DataLink::DBUS {
                        Some(Err(anyhow::anyhow!(
                            "Invalid datalink type `{:?}`",
                            iface.linktype
                        )))
                    } else {
                        self.next()
                    }
                }
                Some(Ok(pcapng::blocks::Block::EnhancedPacket(packet))) => Some(Event::from_bytes(
                    packet.data.to_vec(),
                    packet.timestamp.into(),
                )),
                Some(Ok(_)) => self.next(),
                Some(Err(err)) => Some(Err(err.into())),
                None => None,
            },
        }
    }
}
