//! Public runtime driver primitives for macOS.

use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::io;
use std::os::fd::RawFd;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;

use crate::op::completion::CompletionHandle;

type FdCompletion = CompletionHandle<io::Result<()>>;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub(crate) struct FdReadinessToken(u64);

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub(crate) enum FdInterest {
    Readable,
    Writable,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
struct FdKey {
    fd: RawFd,
    interest: FdInterest,
}

struct FdWaiter {
    token: FdReadinessToken,
    completion: FdCompletion,
}

#[derive(Clone)]
struct NotifierInner {
    write_fd: RawFd,
    closed: Arc<AtomicBool>,
}

impl NotifierInner {
    fn notify(&self) -> io::Result<()> {
        if self.closed.load(Ordering::Acquire) {
            return Err(io::Error::new(
                io::ErrorKind::BrokenPipe,
                "target runtime driver is closed",
            ));
        }

        let byte = 1u8;
        let written = unsafe {
            libc::write(
                self.write_fd,
                &byte as *const u8 as *const libc::c_void,
                std::mem::size_of::<u8>(),
            )
        };
        if written < 0 {
            let error = io::Error::last_os_error();
            if error.kind() == io::ErrorKind::WouldBlock {
                return Ok(());
            }
            return Err(error);
        }

        Ok(())
    }
}

#[derive(Clone)]
/// Cross-thread notifier for a runtime thread's driver.
pub struct ThreadNotifier {
    inner: NotifierInner,
}

impl ThreadNotifier {
    /// Sends a wake notification to the target runtime thread.
    pub fn notify(&self) -> io::Result<()> {
        self.inner.notify()
    }
}

#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
/// Readiness information returned by [`Driver::poll`].
pub struct ReadyEvents {
    /// One or more timer expirations are pending.
    pub timer: bool,
    /// One or more cross-thread wake notifications are pending.
    pub wake: bool,
}

/// Low-level macOS runtime driver backed by `kqueue` and a wake pipe.
pub struct Driver {
    kqueue_fd: RawFd,
    wake_read_fd: RawFd,
    wake_write_fd: RawFd,
    closed: Arc<AtomicBool>,
    timer_deadline: Cell<Option<Duration>>,
    pending_wakes: Cell<u64>,
    pending_timers: Cell<u64>,
    next_fd_token: Cell<u64>,
    fd_waiters: RefCell<HashMap<FdKey, FdWaiter>>,
}

/// Creates a new driver and its paired [`ThreadNotifier`].
pub fn create() -> io::Result<(Driver, ThreadNotifier)> {
    create_driver()
}

/// Creates a new driver and its paired [`ThreadNotifier`].
pub fn create_driver() -> io::Result<(Driver, ThreadNotifier)> {
    let kqueue_fd = cvt(unsafe { libc::kqueue() })?;

    let mut pipe_fds = [0; 2];
    cvt(unsafe { libc::pipe(pipe_fds.as_mut_ptr()) })?;
    let wake_read_fd = pipe_fds[0];
    let wake_write_fd = pipe_fds[1];

    set_nonblocking(wake_read_fd)?;
    set_nonblocking(wake_write_fd)?;

    let event = libc::kevent {
        ident: wake_read_fd as usize,
        filter: libc::EVFILT_READ,
        flags: libc::EV_ADD | libc::EV_ENABLE,
        fflags: 0,
        data: 0,
        udata: std::ptr::null_mut(),
    };

    let submitted = unsafe {
        libc::kevent(
            kqueue_fd,
            &event,
            1,
            std::ptr::null_mut(),
            0,
            std::ptr::null(),
        )
    };
    if submitted < 0 {
        let error = io::Error::last_os_error();
        unsafe {
            libc::close(wake_read_fd);
            libc::close(wake_write_fd);
            libc::close(kqueue_fd);
        }
        return Err(error);
    }

    let closed = Arc::new(AtomicBool::new(false));
    let driver = Driver {
        kqueue_fd,
        wake_read_fd,
        wake_write_fd,
        closed: Arc::clone(&closed),
        timer_deadline: Cell::new(None),
        pending_wakes: Cell::new(0),
        pending_timers: Cell::new(0),
        next_fd_token: Cell::new(1),
        fd_waiters: RefCell::new(HashMap::new()),
    };

    let notifier = ThreadNotifier {
        inner: NotifierInner {
            write_fd: wake_write_fd,
            closed,
        },
    };

    Ok((driver, notifier))
}

impl Driver {
    pub(crate) fn bind_current_thread(&self) {}

    pub(crate) fn unbind_current_thread(&self) {}

    /// Polls the driver without blocking.
    pub fn poll(&self) -> io::Result<Option<ReadyEvents>> {
        let mut pending = ReadyEvents::default();
        if self.pending_wakes.get() > 0 {
            pending.wake = true;
        }
        if self.pending_timers.get() > 0 {
            pending.timer = true;
        }
        if pending.wake || pending.timer {
            return Ok(Some(pending));
        }
        self.process(Some(Duration::ZERO))
    }

    /// Blocks until at least one event is available.
    pub fn wait(&self) -> io::Result<()> {
        let now = monotonic_now()?;
        let timeout = self
            .timer_deadline
            .get()
            .map(|deadline| deadline.saturating_sub(now));
        let _ = self.process(timeout)?;
        Ok(())
    }

    /// Updates the currently armed timer deadline.
    pub fn rearm_timer(&self, deadline: Option<Duration>) -> io::Result<()> {
        self.timer_deadline.set(deadline);
        Ok(())
    }

    /// Drains the accumulated wake notification count.
    pub fn drain_wake(&self) -> io::Result<u64> {
        let wakes = self.pending_wakes.replace(0);
        if wakes == 0 {
            Err(io::Error::new(
                io::ErrorKind::WouldBlock,
                "no wake events are pending",
            ))
        } else {
            Ok(wakes)
        }
    }

    /// Drains the accumulated timer-expiration count.
    pub fn drain_timer(&self) -> io::Result<u64> {
        let timers = self.pending_timers.replace(0);
        if timers == 0 {
            Err(io::Error::new(
                io::ErrorKind::WouldBlock,
                "no timer events are pending",
            ))
        } else {
            Ok(timers)
        }
    }

    pub(crate) fn register_fd_readiness(
        &self,
        fd: RawFd,
        interest: FdInterest,
        completion: FdCompletion,
    ) -> io::Result<FdReadinessToken> {
        let key = FdKey { fd, interest };
        let removed_stale_waiter = {
            let mut waiters = self.fd_waiters.borrow_mut();
            match waiters.get(&key) {
                Some(waiter) if !waiter.completion.is_interested() => {
                    waiters.remove(&key);
                    true
                }
                Some(_) => {
                    return Err(io::Error::new(
                        io::ErrorKind::AlreadyExists,
                        "fd readiness already has a waiter for this interest",
                    ));
                }
                None => false,
            }
        };
        if removed_stale_waiter {
            let _ = self.update_fd_interest(key, libc::EV_DELETE);
        }

        let token = self.allocate_fd_token();
        self.update_fd_interest(key, libc::EV_ADD | libc::EV_ENABLE | libc::EV_ONESHOT)?;
        self.fd_waiters
            .borrow_mut()
            .insert(key, FdWaiter { token, completion });
        Ok(token)
    }

    pub(crate) fn cancel_fd_readiness(&self, token: FdReadinessToken) {
        let mut empty_key = None;
        {
            let mut waiters = self.fd_waiters.borrow_mut();
            for (key, entry) in waiters.iter() {
                if entry.token == token {
                    empty_key = Some(*key);
                    break;
                }
            }

            if let Some(key) = empty_key {
                waiters.remove(&key);
            }
        }

        if let Some(key) = empty_key {
            let _ = self.update_fd_interest(key, libc::EV_DELETE);
        }
    }

    fn process(&self, timeout: Option<Duration>) -> io::Result<Option<ReadyEvents>> {
        let mut ready = ReadyEvents::default();

        let mut events = [unsafe { std::mem::zeroed::<libc::kevent>() }; 16];
        let timeout_spec = timeout_to_timespec(timeout);
        let timeout_ptr = timeout_spec
            .as_ref()
            .map_or(std::ptr::null(), |value| value as *const libc::timespec);

        let result = unsafe {
            libc::kevent(
                self.kqueue_fd,
                std::ptr::null(),
                0,
                events.as_mut_ptr(),
                events.len() as i32,
                timeout_ptr,
            )
        };
        if result < 0 {
            let error = io::Error::last_os_error();
            if error.kind() != io::ErrorKind::Interrupted {
                return Err(error);
            }
        }

        let mut saw_any = false;
        let count = result.max(0) as usize;
        if count > 0 {
            saw_any = true;
            for event in events.iter().take(count) {
                if event.ident as RawFd == self.wake_read_fd {
                    ready.wake = true;
                    let wakes = drain_wake_pipe(self.wake_read_fd)?;
                    self.pending_wakes
                        .set(self.pending_wakes.get().saturating_add(wakes));
                } else if let Some(interest) = interest_from_filter(event.filter) {
                    self.complete_fd_waiters(event.ident as RawFd, interest, event);
                }
            }
        }

        if let Some(deadline) = self.timer_deadline.get()
            && monotonic_now()? >= deadline
        {
            ready.timer = true;
            saw_any = true;
            self.timer_deadline.set(None);
            self.pending_timers
                .set(self.pending_timers.get().saturating_add(1));
        }

        if saw_any { Ok(Some(ready)) } else { Ok(None) }
    }

    fn allocate_fd_token(&self) -> FdReadinessToken {
        let token = self.next_fd_token.get();
        self.next_fd_token.set(
            token
                .checked_add(1)
                .expect("fd readiness token space exhausted"),
        );
        FdReadinessToken(token)
    }

    fn update_fd_interest(&self, key: FdKey, flags: u16) -> io::Result<()> {
        let event = libc::kevent {
            ident: key.fd as usize,
            filter: filter_for_interest(key.interest),
            flags,
            fflags: 0,
            data: 0,
            udata: std::ptr::null_mut(),
        };
        let submitted = unsafe {
            libc::kevent(
                self.kqueue_fd,
                &event,
                1,
                std::ptr::null_mut(),
                0,
                std::ptr::null(),
            )
        };
        if submitted < 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(())
        }
    }

    fn complete_fd_waiters(&self, fd: RawFd, interest: FdInterest, event: &libc::kevent) {
        let key = FdKey { fd, interest };
        let waiter = self.fd_waiters.borrow_mut().remove(&key);
        let Some(waiter) = waiter else {
            return;
        };

        let result = fd_event_result(event, interest);
        waiter.completion.complete(result);
    }
}

impl Drop for Driver {
    fn drop(&mut self) {
        self.closed.store(true, Ordering::Release);
        unsafe {
            libc::close(self.wake_read_fd);
            libc::close(self.wake_write_fd);
            libc::close(self.kqueue_fd);
        }
    }
}

/// Returns the current monotonic clock reading.
pub fn monotonic_now() -> io::Result<Duration> {
    let mut now = std::mem::MaybeUninit::<libc::timespec>::uninit();
    let result = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()) };
    if result < 0 {
        return Err(io::Error::last_os_error());
    }

    let now = unsafe { now.assume_init() };
    Ok(Duration::new(now.tv_sec as u64, now.tv_nsec as u32))
}

fn cvt(value: libc::c_int) -> io::Result<libc::c_int> {
    if value < 0 {
        Err(io::Error::last_os_error())
    } else {
        Ok(value)
    }
}

fn filter_for_interest(interest: FdInterest) -> i16 {
    match interest {
        FdInterest::Readable => libc::EVFILT_READ,
        FdInterest::Writable => libc::EVFILT_WRITE,
    }
}

fn interest_from_filter(filter: i16) -> Option<FdInterest> {
    if filter == libc::EVFILT_READ {
        Some(FdInterest::Readable)
    } else if filter == libc::EVFILT_WRITE {
        Some(FdInterest::Writable)
    } else {
        None
    }
}

fn fd_event_result(event: &libc::kevent, interest: FdInterest) -> io::Result<()> {
    if event.flags & libc::EV_ERROR != 0 && event.data != 0 {
        Err(io::Error::from_raw_os_error(event.data as i32))
    } else if event.flags & libc::EV_EOF != 0 && interest == FdInterest::Writable {
        Err(io::Error::new(
            io::ErrorKind::BrokenPipe,
            "fd write side reached EOF",
        ))
    } else {
        Ok(())
    }
}

fn set_nonblocking(fd: RawFd) -> io::Result<()> {
    let flags = cvt(unsafe { libc::fcntl(fd, libc::F_GETFL) })?;
    cvt(unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) })?;
    Ok(())
}

fn timeout_to_timespec(timeout: Option<Duration>) -> Option<libc::timespec> {
    timeout.map(|value| libc::timespec {
        tv_sec: value.as_secs() as libc::time_t,
        tv_nsec: value.subsec_nanos() as libc::c_long,
    })
}

fn drain_wake_pipe(fd: RawFd) -> io::Result<u64> {
    let mut wakes = 0u64;
    let mut buf = [0u8; 256];

    loop {
        let read = unsafe {
            libc::read(
                fd,
                buf.as_mut_ptr() as *mut libc::c_void,
                buf.len() as libc::size_t,
            )
        };

        if read > 0 {
            wakes = wakes.saturating_add(read as u64);
            continue;
        }

        if read == 0 {
            break;
        }

        let error = io::Error::last_os_error();
        if error.kind() == io::ErrorKind::WouldBlock {
            break;
        }
        if error.kind() == io::ErrorKind::Interrupted {
            continue;
        }

        return Err(error);
    }

    Ok(wakes.max(1))
}
