use std::{
    io::{self, IoSlice, IoSliceMut},
    os::{
        fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
        unix::net::{AncillaryData, SocketAddr, SocketAncillary, UnixDatagram},
    },
    path::{Path, PathBuf},
};

use tempfile::TempDir;

use crate::sys::{self, LocalPacketSocket};

pub struct LocalPacketSocketImpl {
    inner: UnixDatagram,
    #[allow(unused)]
    ephemeral: Option<TempDir>,
}

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct BorrowedAddressImpl<'a>(&'a Path);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OwnedAddressImpl(PathBuf);

impl<'a> sys::BorrowedAddress<'a> for BorrowedAddressImpl<'a> {
    type Owned = OwnedAddressImpl;
}

impl sys::OwnedAddress for OwnedAddressImpl {
    type Borrowed<'a> = BorrowedAddressImpl<'a>;

    fn as_borrowed(&self) -> Self::Borrowed<'_> {
        BorrowedAddressImpl(self.0.as_path())
    }

    fn from_borrowed(borrowed: &Self::Borrowed<'_>) -> Self {
        Self(borrowed.0.into())
    }
}

fn read_ancillary(ancillary: &SocketAncillary) -> Option<OwnedFd> {
    for message in ancillary.messages() {
        let Ok(message) = message else {
            continue;
        };
        let fd = match message {
            AncillaryData::ScmRights(mut rights) => rights.next(),
            _ => None,
        };

        if let Some(fd) = fd {
            return Some(unsafe { OwnedFd::from_raw_fd(fd) });
        }
    }

    None
}

impl LocalPacketSocketImpl {
    pub fn bind_ephemeral() -> io::Result<Self> {
        let temp = TempDir::new()?;
        let path = temp.path().join("temp.sock");
        let inner = UnixDatagram::bind(path)?;
        Ok(Self {
            inner,
            ephemeral: Some(temp),
        })
    }
}

impl LocalPacketSocket for LocalPacketSocketImpl {
    type Address<'a> = BorrowedAddressImpl<'a>;
    type OwnedAddress = OwnedAddressImpl;

    fn new() -> io::Result<Self> {
        let inner = UnixDatagram::unbound()?;
        Ok(Self {
            inner,
            ephemeral: None,
        })
    }

    fn set_nonblocking(&self, nb: bool) -> io::Result<()> {
        self.inner.set_nonblocking(nb)
    }

    fn bind<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        let inner = UnixDatagram::bind(path)?;
        Ok(Self {
            inner,
            ephemeral: None,
        })
    }

    fn connect<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        let this = Self::bind_ephemeral()?;
        this.inner.connect(path)?;
        Ok(this)
    }

    fn send(&self, data: &[u8]) -> io::Result<usize> {
        self.inner.send(data)
    }

    fn send_to<'a>(&self, data: &[u8], address: &Self::Address<'a>) -> io::Result<usize> {
        self.inner.send_to(data, address.0)
    }

    fn send_with_ancillary(&self, data: &[u8], fd: RawFd) -> io::Result<usize> {
        let mut ancillary_buffer = [0; 256];
        let mut ancillary = SocketAncillary::new(&mut ancillary_buffer);
        ancillary.add_fds(&[fd]);
        let buffers = [IoSlice::new(data)];
        let len = self
            .inner
            .send_vectored_with_ancillary(&buffers, &mut ancillary)?;
        Ok(len)
    }

    fn send_with_ancillary_to<'a>(
        &self,
        data: &[u8],
        fd: RawFd,
        address: &Self::Address<'a>,
    ) -> io::Result<usize> {
        let mut ancillary_buffer = [0; 256];
        let mut ancillary = SocketAncillary::new(&mut ancillary_buffer);
        ancillary.add_fds(&[fd]);
        let buffers = [IoSlice::new(data)];
        let len =
            self.inner
                .send_vectored_with_ancillary_to(&buffers, &mut ancillary, address.0)?;
        Ok(len)
    }

    fn receive(&self, data: &mut [u8]) -> io::Result<usize> {
        self.inner.recv(data)
    }

    fn receive_from(&self, data: &mut [u8]) -> io::Result<(usize, Self::OwnedAddress)> {
        let (len, source) = self.inner.recv_from(data)?;
        Ok((len, source.into()))
    }

    fn receive_with_ancillary(&self, data: &mut [u8]) -> io::Result<(usize, Option<OwnedFd>)> {
        let (len, fd, _) = self.receive_with_ancillary_from(data)?;
        Ok((len, fd))
    }

    fn receive_with_ancillary_from(
        &self,
        data: &mut [u8],
    ) -> io::Result<(usize, Option<OwnedFd>, Self::OwnedAddress)> {
        let mut ancillary_buffer = [0; 256];
        let mut ancillary = SocketAncillary::new(&mut ancillary_buffer);
        let mut buffers = [IoSliceMut::new(data)];
        let (len, truncated, source) = self
            .inner
            .recv_vectored_with_ancillary_from(&mut buffers, &mut ancillary)?;
        assert!(!truncated);
        let fd = read_ancillary(&ancillary);
        Ok((len, fd, source.into()))
    }
}

impl AsRawFd for LocalPacketSocketImpl {
    fn as_raw_fd(&self) -> RawFd {
        self.inner.as_raw_fd()
    }
}

impl<'a> From<&'a Path> for BorrowedAddressImpl<'a> {
    fn from(value: &'a Path) -> Self {
        Self(value)
    }
}

impl<'a> From<&'a BorrowedAddressImpl<'a>> for OwnedAddressImpl {
    fn from(value: &'a BorrowedAddressImpl) -> Self {
        Self(value.0.into())
    }
}

impl From<SocketAddr> for OwnedAddressImpl {
    fn from(value: SocketAddr) -> Self {
        let path = value.as_pathname().unwrap();
        Self(path.into())
    }
}
