use crate::netif::NetIf;

#[allow(unused)]
use {
    jlogger_tracing::{jdebug, jerror, jinfo, jwarn, JloggerBuilder, LevelFilter, LogTimeFormat},
    netlink_packet_route::{
        constants::*, rtnl, AddressHeader, AddressMessage, LinkHeader, LinkMessage, NetlinkHeader,
        NetlinkMessage, NetlinkPayload, RtnlMessage, NLM_F_ACK, NLM_F_CREATE, NLM_F_DUMP,
        NLM_F_EXCL, NLM_F_REQUEST,
    },
    rnetmgr_lib::netinfo::{
        MacAddr, NetInfoDelAddress, NetInfoDelLink, NetInfoMessage, NetInfoNewAddress,
        NetInfoNewLink, NetInfoReqMessage, NETINFO_IPCON, NETINFO_IPCON_GROUP,
    },
};

use futures::stream::StreamExt;
use futures::TryStreamExt;
use ipnetwork::{IpNetwork, Ipv4Network};
use rtnetlink::sys::{AsyncSocket, SocketAddr};

use error_stack::{IntoReport, Result, ResultExt};
use std::error::Error;

use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use tokio::sync::mpsc::{self, Sender};

#[cfg(feature = "enable_ipcon")]
use ipcon_sys::{
    ipcon::{IPF_RCV_IF, IPF_SND_IF},
    ipcon_async::AsyncIpcon,
    ipcon_msg::IpconMsg,
};

#[cfg(feature = "enable_ipcon")]
use std::ffi::{CStr, CString};

pub struct NetIfMon {
    netif_hash: HashMap<String, NetIf>,
}

impl Display for NetIfMon {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        for t in self.netif_hash.keys() {
            writeln!(f, "Monitoring {}", t)?
        }
        Ok(())
    }
}

#[cfg(feature = "enable_ipcon")]
async fn netinfo_rcv(ih: &AsyncIpcon) -> (String, Result<NetInfoReqMessage, NetIfMonError>) {
    match ih.receive_msg().await {
        Ok(ipcon_msg) => {
            if let IpconMsg::IpconMsgUser(body) = ipcon_msg {
                #[cfg(target_arch = "x86_64")]
                let buf = unsafe { CStr::from_ptr(body.buf.as_ptr() as *const i8).to_str() };

                #[cfg(target_arch = "aarch64")]
                let buf = unsafe { CStr::from_ptr(body.buf.as_ptr() as *const u8).to_str() };

                match buf {
                    Ok(s) => match serde_json::from_str::<NetInfoReqMessage>(s) {
                        Ok(req) => (body.peer, Ok(req)),
                        Err(e) => (
                            body.peer,
                            Err(NetIfMonError::NetinfoInvalidData)
                                .into_report()
                                .attach_printable_lazy(|| format!("{}", e)),
                        ),
                    },
                    Err(e) => (
                        body.peer,
                        Err(NetIfMonError::NetinfoInvalidData)
                            .into_report()
                            .attach_printable_lazy(|| format!("{}", e)),
                    ),
                }
            } else {
                (
                    String::new(),
                    Err(NetIfMonError::NetinfoInvalidData).into_report(),
                )
            }
        }
        Err(e) => (
            String::new(),
            Err(NetIfMonError::NetinfoInvalidData)
                .into_report()
                .attach_printable_lazy(|| format!("{}", e)),
        ),
    }
}

#[cfg(feature = "enable_ipcon")]
async fn netinfo_send(
    ih: &AsyncIpcon,
    peer: Option<String>,
    msg: NetInfoMessage,
) -> Result<(), NetIfMonError> {
    let buf = serde_json::to_string(&msg)
        .into_report()
        .change_context(NetIfMonError::NetinfoInvalidData)?;

    if let Some(p) = &peer {
        jdebug!("Send to {} : {}", p, buf);
    } else {
        jdebug!("Send multicast : {}", buf);
    }

    let c_buf = CString::new(buf)
        .into_report()
        .change_context(NetIfMonError::NetinfoInvalidData)?;

    if let Some(p) = &peer {
        ih.send_unicast_msg(p, c_buf.as_bytes())
            .await
            .change_context(NetIfMonError::IpconIOError)
    } else {
        ih.send_multicast(NETINFO_IPCON_GROUP, c_buf.as_bytes(), false)
            .await
            .change_context(NetIfMonError::IpconIOError)
    }
}

#[derive(Debug)]
pub enum NetIfMonError {
    RtnlConnectionError,
    RtnlLinkError,

    #[cfg(feature = "enable_ipcon")]
    NetinfoInvalidData,

    #[cfg(feature = "enable_ipcon")]
    IpconIOError,
}

impl Display for NetIfMonError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let error_str = match self {
            NetIfMonError::RtnlLinkError => "RtnlLinkError",
            NetIfMonError::RtnlConnectionError => "RtnlConnectionError",

            #[cfg(feature = "enable_ipcon")]
            NetIfMonError::NetinfoInvalidData => "NetinfoInvalidData",

            #[cfg(feature = "enable_ipcon")]
            NetIfMonError::IpconIOError => "IpconIOError",
        };

        write!(f, "{}", error_str)
    }
}

impl Error for NetIfMonError {}

impl NetIfMon {
    pub async fn run() -> Result<(), NetIfMonError> {
        let (mut conn, handle, mut messages) = rtnetlink::new_connection()
            .into_report()
            .change_context(NetIfMonError::RtnlConnectionError)?;

        let groups = 1 << (RTNLGRP_LINK - 1) | 1 << (RTNLGRP_IPV4_IFADDR - 1);
        jdebug!("group: {}", groups);

        let addr = SocketAddr::new(0, groups);
        let (mut s, mut r) = mpsc::channel(100);
        conn.socket_mut()
            .socket_mut()
            .bind(&addr)
            .into_report()
            .change_context(NetIfMonError::RtnlConnectionError)
            .attach_printable(format!("failed to bind addr {}", addr))?;

        tokio::spawn(conn);

        let mut netif_mon = NetIfMon {
            netif_hash: HashMap::<String, NetIf>::new(),
        };

        /* Dump all Link information */
        let mut links = handle.link().get().execute();
        while let Some(lm) = links
            .try_next()
            .await
            .into_report()
            .change_context(NetIfMonError::RtnlLinkError)?
        {
            let nmsg = NetlinkMessage {
                header: NetlinkHeader::default(),
                payload: NetlinkPayload::InnerMessage(RtnlMessage::NewLink(lm)),
            };

            netif_mon.update(nmsg, Some(&mut s)).await?;
        }

        /* Dump all address information */
        let mut addrs = handle.address().get().execute();
        while let Some(am) = addrs
            .try_next()
            .await
            .into_report()
            .change_context(NetIfMonError::RtnlLinkError)
            .attach_printable("Failed to dump addresses")?
        {
            let nmsg = NetlinkMessage {
                header: NetlinkHeader::default(),
                payload: NetlinkPayload::InnerMessage(RtnlMessage::NewAddress(am)),
            };

            netif_mon.update(nmsg, Some(&mut s)).await?;
        }

        jinfo!("Start monitoring...");

        #[cfg(feature = "enable_ipcon")]
        {
            let ih = AsyncIpcon::new(Some(NETINFO_IPCON), Some(IPF_RCV_IF | IPF_SND_IF))
                .expect("Failed to create IPCON handler.");

            ih.register_group(NETINFO_IPCON_GROUP)
                .await
                .expect("Failed to register rnetmgr group.");

            loop {
                tokio::select! {
                    Some((msg, _)) = messages.next() => {
                            if let Err(e) = netif_mon.update(msg, Some(&mut s)).await {
                                jerror!("{}", e);
                            }
                    }

                    Some(m) = r.recv() => {
                            jinfo!("{}", m);
                            if let Err(e) = netinfo_send(&ih, None, m).await {
                                jerror!("{}", e);
                            }
                    }

                    (peer, ret) = netinfo_rcv(&ih) => {
                        match ret {
                            Ok(m) => {
                                jdebug!("ReqMessage from {}", peer);
                                match m {
                                    NetInfoReqMessage::ReqLink(s) => {
                                            let mut m = NetInfoMessage::NoInfo;

                                            if let Some(nif) = netif_mon.netif_hash.get(&s) {
                                                m = NetInfoMessage::NewLink(NetInfoNewLink {
                                                        ifname : nif.ifname(),
                                                        if_index : nif.if_index(),
                                                        mac : nif.mac().clone(),
                                                        flags : nif.flags(),
                                                });
                                            }

                                            let _ = netinfo_send(&ih, Some(peer), m).await;
                                    }
                                    NetInfoReqMessage::ReqAddress(s) => {
                                            jdebug!("ReqMessage from {} for {}", peer, s);
                                            if let Some(nif) = netif_mon.netif_hash.get(&s) {
                                                let ip_array = nif.ipv4addr();
                                                for ip in ip_array.iter() {
                                                    let m = NetInfoMessage::NewAddress(
                                                        NetInfoNewAddress {
                                                            ifname : nif.ifname(),
                                                            if_index : nif.if_index(),
                                                            ipv4addr : *ip,
                                                    });

                                                    jdebug!("Reply {} in {} to  {}", m, s, peer);
                                                    let _ = netinfo_send(&ih, Some(peer.clone()), m).await;
                                                }
                                            } else {
                                                jdebug!("No IP found for {}", s);
                                            }

                                            let _ = netinfo_send(&ih, Some(peer), NetInfoMessage::NoInfo).await;
                                    }
                                }
                            }
                            Err(e) => {
                                if !peer.is_empty() {
                                    jerror!("Bad NetInfoReqMessage from {} : {}", peer, e);
                                    let _ = netinfo_send(&ih, Some(peer), NetInfoMessage::InvalidReq).await;
                                } else {
                                    jerror!("Unexpected NetInfoReqMessage : {}", e);
                                }
                            }
                        }
                    }

                }
            }
        }

        #[cfg(not(feature = "enable_ipcon"))]
        loop {
            tokio::select! {
                Some((msg, _)) = messages.next() => {
                        if let Err(e) = netif_mon.update(msg, Some(&mut s)).await {
                            jerror!("{}", e);
                        }
                }

                Some(m) = r.recv() => {
                        jinfo!("{}", m);
                }
            }
        }
    }

    async fn update(
        &mut self,
        msg: NetlinkMessage<RtnlMessage>,
        mut sender: Option<&mut Sender<NetInfoMessage>>,
    ) -> Result<bool, NetIfMonError> {
        match msg.payload {
            NetlinkPayload::Done => {
                return Ok(true);
            }

            NetlinkPayload::Error(e) => {
                return Err(NetIfMonError::RtnlLinkError)
                    .into_report()
                    .attach_printable_lazy(|| format!("Payload error: {}", e));
            }

            NetlinkPayload::InnerMessage(RtnlMessage::DelLink(lm)) => {
                let mut ifname = String::new();

                for nla in lm.nlas {
                    if let rtnl::link::nlas::Nla::IfName(name) = nla {
                        ifname = name;
                    }
                }

                if !ifname.is_empty() {
                    let msg = NetInfoMessage::DelLink(NetInfoDelLink {
                        ifname: ifname.clone(),
                        if_index: lm.header.index,
                        flags: lm.header.flags,
                    });

                    jdebug!("{}", msg);

                    if let Some(s) = &mut sender {
                        if let Err(e) = s.send(msg).await {
                            jerror!("MPSC send error: {}", e);
                        }
                    }

                    self.netif_hash.retain(|k, _v| k != &ifname);
                }
            }

            NetlinkPayload::InnerMessage(RtnlMessage::NewLink(lm)) => {
                let mut ifname = String::new();
                let mut mac = MacAddr::default();
                let flags = lm.header.flags;
                let if_index = lm.header.index;

                for nla in lm.nlas {
                    match nla {
                        rtnl::link::nlas::Nla::IfName(name) => {
                            ifname = name.clone();
                        }
                        rtnl::link::nlas::Nla::Address(addr) => {
                            mac = MacAddr::new(addr);
                        }
                        _ => {}
                    }
                }

                if !ifname.is_empty() {
                    let msg = NetInfoMessage::NewLink(NetInfoNewLink {
                        ifname: ifname.clone(),
                        if_index: lm.header.index,
                        mac: mac.clone(),
                        flags: lm.header.flags,
                    });

                    jdebug!("{}", msg);

                    if let Some(s) = &mut sender {
                        if let Err(e) = s.send(msg).await {
                            jerror!("MPSC send error: {}", e);
                        }
                    }

                    self.netif_hash
                        .entry(ifname.clone())
                        .and_modify(|n| {
                            if mac.is_valid() {
                                jwarn!("{} : invalid mac {}", ifname, mac);
                                assert_eq!(mac, *n.mac());
                            }
                            assert_eq!(if_index, n.if_index());
                            n.update_flags(flags);
                        })
                        .or_insert_with(|| NetIf::new(&ifname, if_index, mac, flags));
                }
            }

            NetlinkPayload::InnerMessage(RtnlMessage::DelAddress(am)) => {
                let mut ifname = String::new();
                let mut ipaddr: Option<IpNetwork> = None;

                for nla in am.nlas {
                    match nla {
                        rtnl::address::nlas::Nla::Label(l) => {
                            ifname = l.clone();
                        }
                        rtnl::address::nlas::Nla::Address(addr) => {
                            let ipv4_addr = addr
                                .iter()
                                .map(|a| format! {"{}", a})
                                .collect::<Vec<String>>()
                                .join(".");
                            if let Ok(ip) = ipv4_addr.parse() {
                                let prefix_len = am.header.prefix_len;
                                if let Ok(i) = Ipv4Network::new(ip, prefix_len) {
                                    ipaddr = Some(IpNetwork::V4(i));
                                }
                            }
                        }
                        _ => {}
                    }
                }

                if !ifname.is_empty() {
                    if let Some(addr) = ipaddr {
                        let n = self
                            .netif_hash
                            .entry(ifname.clone())
                            .and_modify(|e| e.del_ipv4_addr(&addr))
                            .or_default();

                        if !n.is_valid() {
                            return Err(NetIfMonError::RtnlLinkError)
                                .into_report()
                                .attach_printable_lazy(|| {
                                    format!("DelAddress: netif {} is not found.", ifname)
                                });
                        }

                        let msg = NetInfoMessage::DelAddress(NetInfoDelAddress {
                            ifname: ifname.clone(),
                            if_index: n.if_index(),
                            ipv4addr: addr,
                        });

                        jdebug!("{}", msg);

                        if let Some(s) = &mut sender {
                            if let Err(e) = s.send(msg).await {
                                jerror!("MPSC send error: {}", e);
                            }
                        }
                    }
                }
            }

            NetlinkPayload::InnerMessage(RtnlMessage::NewAddress(am)) => {
                let mut ifname = String::new();
                let mut ipaddr: Option<IpNetwork> = None;

                for nla in am.nlas {
                    match nla {
                        rtnl::address::nlas::Nla::Label(l) => {
                            ifname = l.clone();
                        }
                        rtnl::address::nlas::Nla::Address(addr) => {
                            let ipv4_addr = addr
                                .iter()
                                .map(|a| format! {"{}", a})
                                .collect::<Vec<String>>()
                                .join(".");
                            if let Ok(ip) = ipv4_addr.parse() {
                                let prefix_len = am.header.prefix_len;
                                if let Ok(i) = Ipv4Network::new(ip, prefix_len) {
                                    ipaddr = Some(IpNetwork::V4(i));
                                }
                            }
                        }
                        _ => {}
                    }
                }

                if !ifname.is_empty() {
                    if let Some(addr) = ipaddr {
                        let netif = self
                            .netif_hash
                            .entry(ifname.clone())
                            .and_modify(|n| n.add_ipv4_addr(&addr))
                            .or_default();

                        if !netif.is_valid() {
                            return Err(NetIfMonError::RtnlLinkError)
                                .into_report()
                                .attach_printable_lazy(|| {
                                    format!("NewAddress: netif {} is not found.", ifname)
                                });
                        }

                        let msg = NetInfoMessage::NewAddress(NetInfoNewAddress {
                            ifname: ifname.clone(),
                            if_index: netif.if_index(),
                            ipv4addr: addr,
                        });

                        jdebug!("{}", msg);

                        if let Some(s) = &mut sender {
                            if let Err(e) = s.send(msg).await {
                                jerror!("MPSC send error: {}", e);
                            }
                        }
                    }
                }
            }
            _ => {
                jdebug!("Unexpected msg");
            }
        }

        Ok(false)
    }
}
