use std::{
  ffi::{CString, c_ulong},
  fmt::Display,
  mem,
  net::{IpAddr, Ipv4Addr, Ipv6Addr},
  os::fd::{AsFd, AsRawFd},
  ptr::addr_of_mut,
};

#[cfg(target_os = "freebsd")]
use nix::ioctl_readwrite;
#[cfg(target_os = "linux")]
use nix::ioctl_readwrite_bad;
use nix::{
  errno::Errno,
  ifaddrs::{InterfaceAddress, getifaddrs},
  libc::{IFNAMSIZ, ifreq},
  net::if_::if_nametoindex,
  sys::socket::{LinkAddr, SockFlag, SockType, socket},
};
use serde::Serialize;

#[derive(Debug, Serialize)]
pub struct InterfaceInfo {
  pub ifindex: u32,
  pub ifname: String,
  pub flags: InterfaceFlags,
  pub mtu: i32,
  pub mac_address: Option<MacAddress>,
  pub broadcast: Option<MacAddress>,
  pub addr_info: Vec<IpAddressInfo>,
}

#[cfg(target_os = "linux")]
const SIOCGIFMTU: c_ulong = nix::libc::SIOCGIFMTU;

#[cfg(target_os = "freebsd")]
const MTU_IOC_MAGIC: u8 = b'i';
#[cfg(target_os = "freebsd")]
const MTU_IOC_TYPE_MODE: u8 = 51;

impl TryFrom<InterfaceAddress> for InterfaceInfo {
  type Error = InterfaceCreationError;
  fn try_from(value: InterfaceAddress) -> Result<Self, Self::Error> {
    TryFrom::<&InterfaceAddress>::try_from(&value)
  }
}

impl TryFrom<&InterfaceAddress> for InterfaceInfo {
  type Error = InterfaceCreationError;
  #[allow(clippy::too_many_lines)]
  fn try_from(value: &InterfaceAddress) -> Result<Self, Self::Error> {
    let ifindex = match if_nametoindex(value.interface_name.as_str()) {
      Ok(index) => index,
      Err(errno) => {
        return Err(InterfaceCreationError {
          ifname: value.interface_name.clone(),
          err: InterfaceCreationErrorType::NameToIndexErr(errno),
        });
      }
    };

    let mtu = match get_mtu(value.interface_name.as_str()) {
      Ok(mtu) => mtu,
      Err(errno) => {
        return Err(InterfaceCreationError {
          ifname: value.interface_name.clone(),
          err: InterfaceCreationErrorType::MtuError(errno),
        });
      }
    };

    let link_address = getifaddrs()
      .map_err(|e| InterfaceCreationError {
        ifname: value.interface_name.clone(),
        err: InterfaceCreationErrorType::InterfaceEnumerationError(e),
      })?
      .find(|interface| {
        interface.interface_name == value.interface_name
          && interface.address.is_some_and(|addr| addr.as_link_addr().is_some())
      })
      .unwrap_or_else(|| panic!("There wasn't a link address interface matching {}", value.interface_name))
      .address
      .expect("No address inside a SockaddrStorage we validated has one")
      .as_link_addr()
      .expect("Can't cast SockaddrStorage to LinkAddr after validating")
      .to_owned();

    let mac_address = link_address.addr().map(Into::into);

    let broadcast_address = match value.broadcast {
      Some(broadcast) => match broadcast.as_link_addr() {
        Some(addr) => match TryInto::<MacAddress>::try_into(*addr) {
          Ok(mac) => Some(mac),
          Err(()) => None,
        },
        None => None,
      },
      None => None,
    };

    let mut addresses: Vec<IpAddressInfo> = Vec::new();
    for interface in getifaddrs()
      .map_err(|e| InterfaceCreationError {
        ifname: value.interface_name.clone(),
        err: InterfaceCreationErrorType::InterfaceEnumerationError(e),
      })?
      .filter(|interface| {
        interface.interface_name == value.interface_name
          && interface.address.is_some_and(|addr| addr.as_sockaddr_in().is_some() || addr.as_sockaddr_in6().is_some())
      })
    {
      let address = interface.address.expect("InterfaceAddress didn't have address field after verification");
      if let Some(ipaddr) = address.as_sockaddr_in() {
        let netmask = interface
          .netmask
          .ok_or(InterfaceCreationError {
            ifname: value.interface_name.clone(),
            err: InterfaceCreationErrorType::NoNetmask,
          })?
          .as_sockaddr_in()
          .expect("Netmask could not be downcast to IP address")
          .to_owned();

        // The cast is allowed to truncate because the netmask can't be longer than 128 bits anyways, which fits into a u8
        #[allow(clippy::cast_possible_truncation)]
        let prefix: u8 = netmask.ip().octets().iter().map(|b| b.count_ones() as u8).sum();

        let broadcast = interface
          .broadcast
          .map(|addr| IpAddr::V4(addr.as_sockaddr_in().expect("No IPv4 address in the IPv4 arm").ip()));

        let addr = IpAddressInfo {
          family: AddressFamily::Inet,
          address: ipaddr.ip().into(),
          prefixlen: prefix,
          broadcast,
          scope: ipaddr.ip().into(),
        };

        addresses.push(addr);
      } else {
        // Address MUST be IPv6 if it isn't v4, because we verified it was one of the two
        let ipaddr = address.as_sockaddr_in6().unwrap();
        let netmask = interface
          .netmask
          .ok_or(InterfaceCreationError {
            ifname: value.interface_name.clone(),
            err: InterfaceCreationErrorType::NoNetmask,
          })?
          .as_sockaddr_in6()
          .expect("Netmask could not be downcast to IPv6 address")
          .to_owned();

        // The cast is allowed to truncate because the netmask can't be longer than 128 bits anyways, which fits into a u8
        #[allow(clippy::cast_possible_truncation)]
        let prefix: u8 = netmask.ip().octets().iter().map(|b| b.count_ones() as u8).sum();

        let broadcast = interface
          .broadcast
          .map(|addr| IpAddr::V6(addr.as_sockaddr_in6().expect("No IPv6 address in the IPv6 arm").ip()));

        let addr = IpAddressInfo {
          family: AddressFamily::Inet6,
          address: ipaddr.ip().into(),
          prefixlen: prefix,
          broadcast,
          scope: ipaddr.ip().into(),
        };

        addresses.push(addr);
      }
    }

    Ok(Self {
      ifindex,
      ifname: value.interface_name.clone(),
      flags: InterfaceFlags(value.flags),
      mtu,
      mac_address,
      broadcast: broadcast_address,
      addr_info: addresses,
    })
  }
}

#[cfg(target_os = "freebsd")]
ioctl_readwrite!(read_socket_mtu, MTU_IOC_MAGIC, MTU_IOC_TYPE_MODE, ifreq);

#[cfg(target_os = "linux")]
ioctl_readwrite_bad!(read_socket_mtu, SIOCGIFMTU, ifreq);

fn get_mtu(name: &str) -> Result<i32, Errno> {
  let Ok(c_string) = CString::new(name) else {
    return Err(Errno::ENOTRECOVERABLE);
  };

  let mut c_chars = [0i8; IFNAMSIZ];
  for (i, &b) in c_string.as_bytes().iter().take(IFNAMSIZ - 1).enumerate() {
    c_chars[i] = match b.try_into() {
      Ok(c) => c,
      Err(_) => return Err(Errno::ENOTRECOVERABLE),
    }
  }

  let mut req: ifreq = unsafe {
    let mut zeroed: ifreq = mem::zeroed();
    zeroed.ifr_name = c_chars;
    zeroed.ifr_ifru.ifru_mtu = 0;
    zeroed
  };

  let sock = socket(AddressFamily::Inet.into(), SockType::Datagram, SockFlag::empty(), None)?;

  match unsafe { read_socket_mtu(sock.as_fd().as_raw_fd(), addr_of_mut!(req)) } {
    Ok(_) => unsafe { Ok(req.ifr_ifru.ifru_mtu) },
    Err(e) => Err(e),
  }
}

#[derive(Debug)]
pub struct InterfaceCreationError {
  pub ifname: String,
  pub err: InterfaceCreationErrorType,
}

#[derive(Debug)]
#[allow(dead_code)] // Dead code is allowed because the linter falsely detects the interior values as dead
pub enum InterfaceCreationErrorType {
  NameToIndexErr(Errno),
  MtuError(Errno),
  InterfaceEnumerationError(Errno),
  NoNetmask,
}

#[derive(Debug, Default)]
pub struct MacAddress([u8; 6]);

impl Display for MacAddress {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    for (i, byte) in self.0.iter().enumerate() {
      if i > 0 {
        write!(f, ":")?;
      }
      write!(f, "{byte:02X}")?;
    }
    Ok(())
  }
}

impl Serialize for MacAddress {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: serde::Serializer,
  {
    serializer.serialize_str(format!("{self}").as_str())
  }
}

impl TryFrom<LinkAddr> for MacAddress {
  type Error = ();
  fn try_from(value: LinkAddr) -> Result<Self, Self::Error> {
    match value.addr() {
      Some(addr) => Ok(Self(addr)),
      None => Err(()),
    }
  }
}

impl From<[u8; 6]> for MacAddress {
  fn from(value: [u8; 6]) -> Self {
    Self(value)
  }
}

impl AsRef<[u8; 6]> for MacAddress {
  fn as_ref(&self) -> &[u8; 6] {
    &self.0
  }
}

#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct IpAddressInfo {
  pub family: AddressFamily,
  pub address: IpAddr,
  pub prefixlen: u8,
  pub broadcast: Option<IpAddr>,
  pub scope: IpScope,
}

#[derive(Debug, Serialize)]
pub enum IpScope {
	#[serde(rename = "global")]
  Global,
  #[serde(rename = "link")]
  LinkLocal,
  #[serde(rename = "host")]
  Host,
}

impl Display for IpScope {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let str_repr = match *self {
      Self::Global => "global",
      Self::LinkLocal => "link",
      Self::Host => "host",
    };
    write!(f, "{str_repr}")
  }
}

impl From<Ipv4Addr> for IpScope {
  fn from(value: Ipv4Addr) -> Self {
    if value.is_link_local() {
      Self::LinkLocal
    } else if value.is_loopback() {
      Self::Host
    } else {
      Self::Global
    }
  }
}

impl From<Ipv6Addr> for IpScope {
  fn from(value: Ipv6Addr) -> Self {
    if value.is_unicast_link_local() {
      Self::LinkLocal
    } else if value.is_loopback() {
      Self::Host
    } else {
      Self::Global
    }
  }
}

pub struct InterfaceFlags(pub nix::net::if_::InterfaceFlags);

impl Serialize for InterfaceFlags {
  fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  where
    S: serde::Serializer,
  {
    use serde::ser::SerializeSeq;
    let flags_str = format!("{}", self.0);
    let flags: Vec<&str> = flags_str.split('|').map(str::trim).collect();
    let mut seq = serializer.serialize_seq(Some(flags.len()))?;
    for flag in flags {
      seq.serialize_element(flag)?;
    }
    seq.end()
  }
}

impl std::fmt::Debug for InterfaceFlags {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{:?}", self.0)
  }
}

impl Display for InterfaceFlags {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    write!(f, "{}", self.0)
  }
}

#[derive(Debug, Serialize)]
pub enum AddressFamily {
	#[serde(rename = "inet")]
  Inet,
  #[serde(rename = "inet6")]
  Inet6,
}

impl Display for AddressFamily {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    let str_repr = match *self {
      Self::Inet => "inet",
      Self::Inet6 => "inet6",
    };
    write!(f, "{str_repr}")
  }
}

impl From<AddressFamily> for nix::sys::socket::AddressFamily {
  fn from(value: AddressFamily) -> Self {
    use nix::sys::socket::AddressFamily as af;
    match value {
      AddressFamily::Inet => af::Inet,
      AddressFamily::Inet6 => af::Inet6,
    }
  }
}
