use core::{
    mem::size_of,
    net::{IpAddr, SocketAddr},
};

use yggdrasil_abi::{
    error::Error,
    net::{
        protocols::{IpProtocol, UdpFrame},
        types::NetValueImpl,
    },
};

use crate::{l3, socket::UdpSocket, L3Packet};

pub async fn send(
    source_port: u16,
    destination_ip: IpAddr,
    destination_port: u16,
    ttl: u8,
    data: &[u8],
) -> Result<(), Error> {
    let length: u16 = (data.len() + size_of::<UdpFrame>()).try_into().unwrap();
    let udp_frame = UdpFrame {
        source_port: source_port.to_network_order(),
        destination_port: destination_port.to_network_order(),
        length: length.to_network_order(),
        checksum: 0u16.to_network_order(),
    };

    l3::send_l4_ip(&l3::L4UnresolvedPacket {
        destination_ip,
        l4_frame: bytemuck::bytes_of(&udp_frame),
        l4_options: &[],
        l4_data: data,
        protocol: IpProtocol::UDP,
        ttl,
    })
    .await
}

// pub fn send_broadcast(
//     v6: bool,
//     source_port: u16,
//     destination_port: u16,
//     data: &[u8],
// ) -> Result<(), Error> {
//     let length: u16 = (data.len() + size_of::<UdpFrame>()).try_into().unwrap();
//     let udp_frame = UdpFrame {
//         source_port: source_port.to_network_order(),
//         destination_port: destination_port.to_network_order(),
//         length: length.to_network_order(),
//         checksum: 0u16.to_network_order(),
//     };
//
//     l3::send_l4_ip_broadcast(v6, IpProtocol::UDP, &udp_frame, data)
// }

pub fn handle(l3_packet: L3Packet) -> Result<(), Error> {
    if l3_packet.data_length < size_of::<UdpFrame>() {
        log::warn!("Truncated UDP frame received");
        return Err(Error::MissingData);
    }

    let l3_data = l3_packet.l3_data();

    let udp_frame: &UdpFrame = bytemuck::from_bytes(&l3_data[..size_of::<UdpFrame>()]);
    let data_size = core::cmp::min(
        udp_frame.data_length(),
        l3_packet.data_length - size_of::<UdpFrame>(),
    );
    let udp_data = &l3_data[size_of::<UdpFrame>()..data_size + size_of::<UdpFrame>()];

    let source = SocketAddr::new(
        l3_packet.source_address,
        u16::from_network_order(udp_frame.source_port),
    );
    let destination = SocketAddr::new(
        l3_packet.destination_address,
        u16::from_network_order(udp_frame.destination_port),
    );

    if let Some(socket) = UdpSocket::get(&destination) {
        socket.packet_received(source, udp_data).ok();
    }

    Ok(())
}
