use std::net::{IpAddr, Ipv4Addr, SocketAddr};

use yggdrasil_abi::net::{
    protocols::{EtherType, EthernetFrame, IpProtocol, Ipv4Frame, UdpFrame},
    types::NetValueImpl,
};

// pub mod http;

pub fn parse_l2_protocol(packet: &[u8]) -> Option<(EthernetFrame, &[u8])> {
    if packet.len() < size_of::<EthernetFrame>() {
        return None;
    }

    let l2_frame: &EthernetFrame = bytemuck::from_bytes(&packet[..size_of::<EthernetFrame>()]);
    let l2_data = &packet[size_of::<EthernetFrame>()..];

    Some((*l2_frame, l2_data))
}

pub fn parse_ip_protocol(packet: &[u8]) -> Option<(IpProtocol, IpAddr, IpAddr, &[u8])> {
    let (l2_frame, l2_data) = parse_l2_protocol(packet)?;

    match EtherType::from_network_order(l2_frame.ethertype) {
        EtherType::IPV4 if l2_data.len() >= size_of::<Ipv4Frame>() => {
            let l3_frame: &Ipv4Frame = bytemuck::from_bytes(&l2_data[..size_of::<Ipv4Frame>()]);

            let source_addr = IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
                l3_frame.source_address,
            )));
            let destination_addr = IpAddr::V4(Ipv4Addr::from(u32::from_network_order(
                l3_frame.destination_address,
            )));

            Some((
                l3_frame.protocol,
                source_addr,
                destination_addr,
                &l2_data[l3_frame.header_length()..l3_frame.total_length()],
            ))
        }
        _ => None,
    }
}

pub fn parse_udp_protocol(packet: &[u8]) -> Option<(SocketAddr, SocketAddr, &[u8])> {
    let (protocol, source_ip, destination_ip, l3_data) = parse_ip_protocol(packet)?;

    if protocol != IpProtocol::UDP || l3_data.len() < size_of::<UdpFrame>() {
        return None;
    }

    let l4_frame: &UdpFrame = bytemuck::from_bytes(&l3_data[..size_of::<UdpFrame>()]);
    let l4_data_size = core::cmp::min(
        l3_data.len() - size_of::<UdpFrame>(),
        l4_frame.data_length(),
    );

    let source_addr = SocketAddr::new(source_ip, u16::from_network_order(l4_frame.source_port));
    let destination_addr = SocketAddr::new(
        destination_ip,
        u16::from_network_order(l4_frame.destination_port),
    );

    Some((
        source_addr,
        destination_addr,
        &l3_data[size_of::<UdpFrame>()..size_of::<UdpFrame>() + l4_data_size],
    ))
}
