use error_stack::{IntoReport, Result, ResultExt};
use ipnetwork::IpNetwork;
use std::collections::HashMap;
use std::error::Error;
use std::ffi::{CStr, CString};
use std::fmt::Display;
use std::sync::mpsc::Sender;
use std::thread::JoinHandle;

use {
    ipcon_sys::ipcon::{self, Ipcon, IPF_RCV_IF, IPF_SND_IF},
    ipcon_sys::ipcon_msg::{IpconMsg, IpconMsgType},
};

use super::netinfo::{NetInfoMessage, NetInfoReqMessage, NETINFO_IPCON, NETINFO_IPCON_GROUP};

#[derive(Debug)]
pub enum RnetmgrConError {
    InvalidData,
    IpconError,
    IOError,
    Unexpected,
}

impl Display for RnetmgrConError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let err_str = match self {
            RnetmgrConError::InvalidData => "Invalid data",
            RnetmgrConError::IOError => "IO error",
            RnetmgrConError::IpconError => "Ipcon error",
            _ => "Unexpected error",
        };

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

impl Error for RnetmgrConError {}

const NET_INFO_REPORT_IFACE: &str = "eth0";
pub struct NetInfoReport {
    if_name: String,
    ipv4addr: Vec<IpNetwork>,
}

impl NetInfoReport {
    pub fn new(if_name: String) -> Self {
        NetInfoReport {
            if_name,
            ipv4addr: Vec::<IpNetwork>::new(),
        }
    }

    pub fn add(&mut self, ip: IpNetwork) {
        if !self.ipv4addr.iter().any(|a| a == &ip) {
            println!(
                "NetInfoManage: {} add {}/{}",
                self.if_name,
                ip.ip().to_string(),
                ip.prefix()
            );
            self.ipv4addr.push(ip);
        }
    }

    pub fn del(&mut self, ip: IpNetwork) {
        println!(
            "NetInfoManage: {} del {}/{}",
            self.if_name,
            ip.ip().to_string(),
            ip.prefix()
        );
        self.ipv4addr.retain(|a| a != &ip);
    }

    pub fn get(&self) -> Option<String> {
        let a = self.ipv4addr.iter().next()?;
        Some(format!(
            "{} : {}/{}",
            self.if_name,
            a.ip().to_string(),
            a.prefix()
        ))
    }

    pub fn if_name(&self) -> &str {
        &self.if_name
    }
}

pub struct RnetmgrCon {}

impl RnetmgrCon {
    pub fn run(
        name: Option<&'static str>,
        sender: Sender<Option<String>>,
    ) -> Result<JoinHandle<Result<(), RnetmgrConError>>, RnetmgrConError> {
        let handler = std::thread::spawn(move || -> Result<(), RnetmgrConError> {
            let mut netinfo_hash = HashMap::<String, NetInfoReport>::new();
            let ih = Ipcon::new(name, Some(IPF_SND_IF | IPF_RCV_IF))
                .change_context(RnetmgrConError::IpconError)?;

            ih.join_group(ipcon::IPCON_KERNEL_NAME, ipcon::IPCON_KERNEL_GROUP_NAME)
                .change_context(RnetmgrConError::IpconError)?;

            let try_send_request = || -> Result<(), RnetmgrConError> {
                ih.join_group(NETINFO_IPCON, NETINFO_IPCON_GROUP)
                    .change_context(RnetmgrConError::IpconError)?;

                let req_msg = NetInfoReqMessage::ReqAddress(String::from(NET_INFO_REPORT_IFACE));
                let buf = serde_json::to_string(&req_msg)
                    .into_report()
                    .change_context(RnetmgrConError::InvalidData)?;

                let c_buf = CString::new(buf)
                    .into_report()
                    .change_context(RnetmgrConError::InvalidData)?;

                ih.send_unicast_msg(NETINFO_IPCON, c_buf.as_bytes())
                    .change_context(RnetmgrConError::IpconError)
            };

            let _ = try_send_request();
            loop {
                let msg = ih
                    .receive_msg()
                    .change_context(RnetmgrConError::IpconError)?;

                match msg {
                    IpconMsg::IpconMsgKevent(kevent) => {
                        if let Some((p, g)) = kevent.group_added() {
                            if (NETINFO_IPCON == p) && (NETINFO_IPCON_GROUP == g) {
                                let _ = try_send_request();
                            }

                            continue;
                        }

                        if let Some((p, g)) = kevent.group_removed() {
                            if (NETINFO_IPCON == p) && (NETINFO_IPCON_GROUP == g) {
                                netinfo_hash.clear();
                                sender
                                    .send(None)
                                    .into_report()
                                    .change_context(RnetmgrConError::IpconError)?;
                            }

                            continue;
                        }
                    }

                    IpconMsg::IpconMsgUser(m)
                        if matches!(m.msg_type, IpconMsgType::IpconMsgTypeGroup) =>
                    {
                        let group = m.group.unwrap();
                        if (NETINFO_IPCON == m.peer) && (NETINFO_IPCON_GROUP == group) {
                            #[cfg(target_arch = "x86_64")]
                            let c_str =
                                unsafe { CStr::from_ptr(m.buf.as_ptr() as *const i8).to_str() };

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

                            match c_str {
                                Ok(s) => match serde_json::from_str::<NetInfoMessage>(s) {
                                    Ok(NetInfoMessage::NewAddress(addr))
                                        if addr.ifname.eq(NET_INFO_REPORT_IFACE) =>
                                    {
                                        let nim = netinfo_hash
                                            .entry(String::from(NET_INFO_REPORT_IFACE))
                                            .or_insert_with(|| {
                                                NetInfoReport::new(String::from(
                                                    NET_INFO_REPORT_IFACE,
                                                ))
                                            });

                                        nim.add(addr.ipv4addr);

                                        sender
                                            .send(nim.get())
                                            .into_report()
                                            .change_context(RnetmgrConError::IOError)?;
                                    }

                                    Ok(NetInfoMessage::DelAddress(addr))
                                        if addr.ifname.eq(NET_INFO_REPORT_IFACE) =>
                                    {
                                        let nim = netinfo_hash
                                            .entry(String::from(NET_INFO_REPORT_IFACE))
                                            .or_insert_with(|| {
                                                NetInfoReport::new(String::from(
                                                    NET_INFO_REPORT_IFACE,
                                                ))
                                            });

                                        nim.del(addr.ipv4addr);

                                        sender
                                            .send(nim.get())
                                            .into_report()
                                            .change_context(RnetmgrConError::IOError)?;
                                    }

                                    _ => {}
                                },
                                _ => {}
                            }
                        }
                    }
                    IpconMsg::IpconMsgUser(m)
                        if matches!(m.msg_type, IpconMsgType::IpconMsgTypeNormal)
                            && (NETINFO_IPCON == m.peer) =>
                    {
                        #[cfg(target_arch = "x86_64")]
                        let c_str = unsafe { CStr::from_ptr(m.buf.as_ptr() as *const i8).to_str() };

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

                        match c_str {
                            Ok(s) => match serde_json::from_str::<NetInfoMessage>(s) {
                                Ok(NetInfoMessage::NewAddress(addr))
                                    if addr.ifname.eq(NET_INFO_REPORT_IFACE) =>
                                {
                                    let nim = netinfo_hash
                                        .entry(String::from(NET_INFO_REPORT_IFACE))
                                        .or_insert_with(|| {
                                            NetInfoReport::new(String::from(NET_INFO_REPORT_IFACE))
                                        });

                                    nim.add(addr.ipv4addr);

                                    sender
                                        .send(nim.get())
                                        .into_report()
                                        .change_context(RnetmgrConError::IOError)?;
                                }

                                _ => {}
                            },
                            _ => {}
                        }
                    }
                    _ => {}
                }
            }
        });

        Ok(handler)
    }
}
