use std::{
    net::IpAddr,
    ops::{Deref, DerefMut},
};

use anyhow::{anyhow, Result};

use crate::{
    core::message::Message,
    types::{
        message::{Attribute, NeighborMessage, RouteAttr},
        neigh::Neighbor,
    },
};

use super::handle::SocketHandle;

pub struct NeighHandle<'a> {
    pub socket: &'a mut SocketHandle,
}

impl<'a> Deref for NeighHandle<'a> {
    type Target = SocketHandle;

    fn deref(&self) -> &Self::Target {
        self.socket
    }
}

impl<'a> DerefMut for NeighHandle<'a> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        self.socket
    }
}

impl<'a> From<&'a mut SocketHandle> for NeighHandle<'a> {
    fn from(socket: &'a mut SocketHandle) -> Self {
        Self { socket }
    }
}

impl NeighHandle<'_> {
    pub fn handle(&mut self, neigh: &Neighbor, proto: u16, flags: i32) -> Result<()> {
        let mut req = Message::new(proto, flags);

        let (family, ip_addr_vec) = match neigh.ip_addr {
            Some(IpAddr::V4(ip)) => (libc::AF_INET as u8, ip.octets().to_vec()),
            Some(IpAddr::V6(ip)) => (libc::AF_INET6 as u8, ip.octets().to_vec()),
            None => return Err(anyhow!("IP address is required")),
        };

        let family = neigh.family.map_or(family, |f| f);

        let neigh_msg = NeighborMessage::new(
            family,
            neigh.link_index,
            neigh.state,
            neigh.flags,
            neigh.neigh_type,
        );

        let destination = RouteAttr::new(libc::NDA_DST, &ip_addr_vec);

        req.add(&neigh_msg.serialize()?);
        req.add(&destination.serialize()?);

        if let Some(mac_addr) = &neigh.mac_addr {
            let mac = RouteAttr::new(libc::NDA_LLADDR, mac_addr);
            req.add(&mac.serialize()?);
        }

        self.request(&mut req, 0)?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        parse_mac, test_setup,
        types::{
            link::{Kind, LinkAttrs},
            neigh::NeighborBuilder,
        },
    };

    use super::*;

    #[test]
    fn test_neigh_handle() {
        test_setup!();
        let mut handle = SocketHandle::new(libc::NETLINK_ROUTE);

        let mut link_handle = handle.handle_link();
        let attr = LinkAttrs::new("foo");

        let link = Kind::Dummy(attr.clone());

        link_handle
            .add(
                &link,
                libc::NLM_F_CREATE | libc::NLM_F_EXCL | libc::NLM_F_ACK,
            )
            .unwrap();

        let link = link_handle.get(&attr).unwrap();

        let mut neigh_handle = handle.handle_neigh();

        let mac_bytes = parse_mac("aa:bb:cc:dd:00:01").unwrap();

        let neigh = NeighborBuilder::default()
            .link_index(link.attrs().index as u32)
            .state(libc::NUD_PERMANENT)
            .neigh_type(libc::RTN_UNICAST)
            .ip_addr(Some(IpAddr::V4("10.244.0.0".parse().unwrap())))
            .mac_addr(Some(mac_bytes))
            .build()
            .unwrap();

        neigh_handle
            .handle(
                &neigh,
                libc::RTM_NEWNEIGH,
                libc::NLM_F_CREATE | libc::NLM_F_REPLACE | libc::NLM_F_ACK,
            )
            .unwrap();
    }
}
