use core::{ffi::c_int, net::SocketAddr};

use yggdrasil_abi::abi_serde::wire;
use yggdrasil_rt::{io::RawFd, net as rt, sys as syscall};

use crate::{
    error::{self, CFdResult, CIntCountResult, CIntZeroResult, CResult, ResultExt, TryFromExt},
    headers::{
        errno::Errno,
        sys_socket::{
            SocketAddrExt, AF_INET, SHUT_RD, SHUT_RDWR, SHUT_WR, SOCK_DGRAM, SOCK_STREAM,
        },
    },
};

use super::{sockaddr, socklen_t};

#[no_mangle]
unsafe extern "C" fn accept(fd: c_int, remote: *mut sockaddr, len: *mut socklen_t) -> CFdResult {
    let listener_fd = RawFd::e_try_from(fd)?;
    let stream_fd = if remote.is_null() || len.is_null() {
        syscall::accept(listener_fd, None).e_map_err(Errno::from)?
    } else {
        let mut sockaddr_buf = [0; 32];
        let stream_fd =
            syscall::accept(listener_fd, Some(&mut sockaddr_buf)).e_map_err(Errno::from)?;
        let sockaddr: SocketAddr = wire::from_slice(&sockaddr_buf)
            .map_err(yggdrasil_rt::Error::from)
            .e_map_err(Errno::from)?;
        *len = sockaddr.to_c(remote, *len)?;
        stream_fd
    };
    CFdResult::success(stream_fd)
}

#[no_mangle]
unsafe extern "C" fn bind(fd: c_int, local: *const sockaddr, len: socklen_t) -> CIntZeroResult {
    let mut sockaddr_buf = [0; 32];
    let fd = RawFd::e_try_from(fd)?;
    let local = local.to_rust(len)?;
    let sockaddr_len = wire::to_slice(&local, &mut sockaddr_buf)
        .map_err(yggdrasil_rt::Error::from)
        .e_map_err(Errno::from)?;
    syscall::bind(fd, &sockaddr_buf[..sockaddr_len]).e_map_err(Errno::from)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn connect(fd: c_int, remote: *const sockaddr, len: socklen_t) -> CIntZeroResult {
    let fd = RawFd::e_try_from(fd)?;
    let mut sockaddr_buf = [0; 256];
    let remote = remote.to_rust(len)?;
    let sockaddr_len = wire::to_slice(&remote, &mut sockaddr_buf)
        .map_err(yggdrasil_rt::Error::from)
        .e_map_err(Errno::from)?;
    syscall::connect(fd, &sockaddr_buf[..sockaddr_len]).e_map_err(Errno::from)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn listen(fd: c_int, backlog: c_int) -> CIntZeroResult {
    let _ = backlog;
    let fd = RawFd::e_try_from(fd)?;
    syscall::listen(fd).e_map_err(Errno::from)?;
    CIntZeroResult::SUCCESS
}

#[no_mangle]
unsafe extern "C" fn shutdown(fd: c_int, how: c_int) -> CIntZeroResult {
    let fd = RawFd::e_try_from(fd)?;
    let how = match how {
        SHUT_RD => rt::SocketShutdown::READ,
        SHUT_WR => rt::SocketShutdown::WRITE,
        SHUT_RDWR => rt::SocketShutdown::READ | rt::SocketShutdown::WRITE,
        _ => {
            error::set_errno(Errno::EINVAL);
            return CIntZeroResult::ERROR;
        }
    };

    syscall::shutdown(fd, how).e_map_err(Errno::from)?;

    CIntZeroResult::SUCCESS
}

// TODO out-of-band data in sockets
#[no_mangle]
unsafe extern "C" fn sockatmark(_fd: c_int) -> CIntCountResult {
    error::set_errno(Errno::EINVAL);
    CIntCountResult::ERROR
}

#[no_mangle]
unsafe extern "C" fn socket(domain: c_int, ty: c_int, proto: c_int) -> CFdResult {
    let ty = match (domain, ty, proto) {
        (AF_INET, SOCK_STREAM, 0) => rt::SocketType::TcpStream,
        (AF_INET, SOCK_DGRAM, 0) => rt::SocketType::UdpPacket,
        (_, _, _) => {
            log::error!("Unsupported socket({domain}, {ty}, {proto})");
            error::set_errno(Errno::ENOTSUPP);
            return CFdResult::ERROR;
        }
    };

    let fd = syscall::create_socket(ty).e_map_err(Errno::from)?;
    CFdResult::success(fd)
}

#[no_mangle]
unsafe extern "C" fn socketpair(
    domain: c_int,
    ty: c_int,
    proto: c_int,
    sockets: *mut c_int,
) -> c_int {
    let _ = domain;
    let _ = ty;
    let _ = proto;
    let _ = sockets;
    todo!()
}
