use crate::Error;

use std::{
    net::IpAddr,
    os::fd::{AsRawFd, FromRawFd, OwnedFd},
};

use runtime::{
    abi::net::{self, netconfig},
    abi_serde::{wire, Deserialize},
    rt,
};

pub struct NetConfig {
    socket: OwnedFd,
    buffer: [u8; 4096],
}

impl NetConfig {
    pub fn open() -> Result<Self, Error> {
        let raw = unsafe { rt::sys::create_socket(rt::abi::net::SocketType::NetConfig) }
            .map_err(Error::RtError)?;
        let fd = unsafe { OwnedFd::from_raw_fd(raw) };
        Ok(Self {
            socket: fd,
            buffer: [0; 4096],
        })
    }

    fn send_inner(&mut self, request: &netconfig::NetConfigRequest) -> Result<(), rt::Error> {
        let len = wire::to_slice(request, &mut self.buffer)?;
        rt::net::socket::send(self.socket.as_raw_fd(), &self.buffer[..len])?;
        Ok(())
    }

    fn receive_inner<'de, T: Deserialize<'de>>(
        &'de mut self,
    ) -> Result<netconfig::NetConfigResult<'de, T>, rt::Error> {
        let len = rt::net::socket::receive(self.socket.as_raw_fd(), &mut self.buffer)?;
        let value = wire::from_slice(&self.buffer[..len]).unwrap();
        Ok(value)
    }

    pub fn send(&mut self, request: &netconfig::NetConfigRequest) -> Result<(), Error> {
        self.send_inner(request).map_err(Error::RtError)
    }

    pub fn request<'de, T: Deserialize<'de>>(
        &'de mut self,
        request: &netconfig::NetConfigRequest,
    ) -> Result<T, Error> {
        self.send(request)?;
        match self.receive_inner().map_err(Error::RtError)? {
            netconfig::NetConfigResult::Ok(value) => Ok(value),
            netconfig::NetConfigResult::Err(error) => Err(Error::NetConfError(error.into())),
        }
    }

    pub fn query_route(&mut self, address: IpAddr) -> Result<netconfig::RoutingInfo, Error> {
        self.request(&netconfig::NetConfigRequest::QueryRoute(address))
    }

    pub fn query_arp<'a, Q: Into<net::SocketInterfaceQuery<'a>>>(
        &mut self,
        query: Q,
        address: IpAddr,
        cache_only: bool,
    ) -> Result<net::MacAddress, Error> {
        self.request(&netconfig::NetConfigRequest::ArpQuery(
            netconfig::ArpQueryRequest {
                interface: query.into(),
                address,
                cache_only,
            },
        ))
    }

    pub fn list_interfaces(&mut self) -> Result<Vec<netconfig::InterfaceBinding>, Error> {
        self.request(&netconfig::NetConfigRequest::ListInterfaces(()))
    }

    pub fn describe_interface<'a, Q: Into<net::SocketInterfaceQuery<'a>>>(
        &mut self,
        query: Q,
    ) -> Result<netconfig::InterfaceInfo, Error> {
        self.request(&netconfig::NetConfigRequest::DescribeInterface(
            query.into(),
        ))
    }

    pub fn list_routes(&mut self) -> Result<Vec<netconfig::RouteInfo>, Error> {
        self.request(&netconfig::NetConfigRequest::ListRoutes(()))
    }

    pub fn describe_routes<'a, Q: Into<net::SocketInterfaceQuery<'a>>>(
        &mut self,
        query: Q,
    ) -> Result<Vec<netconfig::RouteInfo>, Error> {
        self.request(&netconfig::NetConfigRequest::DescribeRoutes(query.into()))
    }

    pub fn set_interface_address<'a, Q: Into<net::SocketInterfaceQuery<'a>>>(
        &mut self,
        query: Q,
        address: IpAddr,
    ) -> Result<(), Error> {
        self.request(&netconfig::NetConfigRequest::SetNetworkAddress(
            netconfig::SetNetworkAddressRequest {
                interface: query.into(),
                address,
            },
        ))
    }

    pub fn add_route<'a, Q: Into<net::SocketInterfaceQuery<'a>>>(
        &mut self,
        query: Q,
        subnet: net::SubnetAddr,
        gateway: Option<IpAddr>,
    ) -> Result<(), Error> {
        self.request(&netconfig::NetConfigRequest::AddRoute(
            netconfig::AddRouteRequest {
                interface: query.into(),
                gateway,
                subnet,
            },
        ))
    }
}
