use crate::NetlinkRouteHandle;
#[allow(unused)]
use crate::addr::{AddrGetInterface, AddrGetInterfaceAddressV4, AddrGetResponse, AddressFlags};
use crate::error::Error;
#[cfg(all(target_os = "linux", feature = "netlink"))]
use neli::{
    consts::{
        nl::NlmF,
        rtnl::{Ifa, RtAddrFamily, RtScope, Rtm},
    },
    nl::{NlPayload, Nlmsghdr},
    router::synchronous::NlRouterReceiverHandle,
    rtnl::{Ifaddrmsg, IfaddrmsgBuilder},
};
use std::collections::BTreeMap;

pub struct AddrGetRequest {
    if_index: Option<u32>,
}

impl AddrGetRequest {
    #[cfg(not(all(target_os = "linux", feature = "netlink")))]
    pub fn for_ifname(_ifname: &str) -> Result<Self, Error> {
        Err(Error::NotImplemented)
    }
    #[cfg(all(target_os = "linux", feature = "netlink"))]
    pub fn for_ifname(ifname: &str) -> Result<Self, Error> {
        use nix::net::if_::if_nametoindex;
        let if_index = if_nametoindex(ifname).map_err(|e| Error::InterfaceLookup {
            ifname: ifname.to_owned(),
            source: e,
        })?;
        Ok(AddrGetRequest {
            if_index: Some(if_index),
        })
    }

    pub fn all() -> Self {
        AddrGetRequest { if_index: None }
    }

    #[cfg(not(all(target_os = "linux", feature = "netlink")))]
    pub fn send(&self, _h: &mut NetlinkRouteHandle) -> Result<AddrGetResponse, Error> {
        Err(Error::NotImplemented)
    }
    #[cfg(all(target_os = "linux", feature = "netlink"))]
    pub fn send(&self, h: &mut NetlinkRouteHandle) -> Result<AddrGetResponse, Error> {
        use crate::util::mappers::ip::{rtattr_to_ipv4, rtattr_to_string};
        use nix::net::if_::if_indextoname;

        let ifaddrmsg = IfaddrmsgBuilder::default()
            .ifa_family(RtAddrFamily::Inet)
            .ifa_prefixlen(0)
            .ifa_scope(RtScope::Universe)
            .ifa_index(self.if_index.unwrap_or(0))
            .build()?;
        let recv: NlRouterReceiverHandle<Rtm, Ifaddrmsg> = h
            .rtnl
            .send(Rtm::Getaddr, NlmF::DUMP, NlPayload::Payload(ifaddrmsg))
            .map_err(|e| Error::Send(Box::new(e)))?;

        let mut interfaces_by_index = BTreeMap::new();

        for response in recv {
            let header: Nlmsghdr<Rtm, Ifaddrmsg> =
                response.map_err(|e| Error::Receive(Box::new(e)))?;
            if let NlPayload::Payload(p) = header.nl_payload() {
                if header.nl_type() != &Rtm::Newaddr {
                    return Err(Error::UnexpectedNlType {
                        expected: format!("{:?}", Rtm::Newaddr),
                        actual: format!("{:?}", header.nl_type()),
                    });
                }

                let if_index: u32 = *p.ifa_index();

                let mut interface =
                    interfaces_by_index
                        .remove(&if_index)
                        .unwrap_or_else(|| AddrGetInterface {
                            if_name: if_indextoname(if_index)
                                .ok()
                                .and_then(|n| n.into_string().ok()),
                            addresses: vec![],
                        });

                let family = *p.ifa_family();
                let if_name = if_indextoname(if_index)
                    .ok()
                    .and_then(|n| n.into_string().ok());
                let prefix_len: u8 = *p.ifa_prefixlen();

                println!(
                    "family: {:?} idx={if_index} if_name={:?} scope={:?} prefix={prefix_len}",
                    family,
                    if_name,
                    p.ifa_scope()
                );

                let mut local = None;
                let mut address = None;
                let mut broadcast = None;
                let mut label = None;
                let flags = AddressFlags::from(*p.ifa_flags());
                for rtattr in p.rtattrs().iter() {
                    match *rtattr.rta_type() {
                        Ifa::Local => local = Some(rtattr_to_ipv4(rtattr)?),
                        Ifa::Address => address = Some(rtattr_to_ipv4(rtattr)?),
                        Ifa::Broadcast => broadcast = Some(rtattr_to_ipv4(rtattr)?),
                        Ifa::Label => label = Some(rtattr_to_string(rtattr)?),
                        _other => {
                            //    println!("{:?}:{:?}", _other, rtattr.payload().as_ref());
                        }
                    }
                }

                interface.addresses.push(AddrGetInterfaceAddressV4 {
                    prefix_len,
                    flags,
                    local,
                    address,
                    broadcast,
                    label,
                });

                interfaces_by_index.insert(if_index, interface);
                //println!("---\n");
            }
        }

        if let Some(if_index) = self.if_index
            && interfaces_by_index.is_empty()
        {
            // if the user requested a specific interface but it doesn't have any addresses,
            // it won't return any payloads above. Crate an entry for it in the map anyway so that
            // the user can use .get_only()
            interfaces_by_index.insert(
                if_index,
                AddrGetInterface {
                    if_name: if_indextoname(if_index)
                        .map_err(|e| Error::IfIndexLookup {
                            ifindex: if_index,
                            source: e,
                        })?
                        .into_string()
                        .ok(),
                    addresses: vec![],
                },
            );
        }

        Ok(AddrGetResponse {
            interfaces: interfaces_by_index,
        })
    }
}
