//! BIO wrappers — `MemBio`, `MemBioBuf<'a>`, `Bio`, `BorrowedBio<'_>`.
//!
//! BIOs are OpenSSL's generic I/O abstraction.  This module exposes four types:
//!
//! - [`MemBio`] — a writable, growable in-memory BIO (`BIO_s_mem()`).  Used for
//!   encoding output (PEM, DER).  Call `data()` after writing to read the result
//!   as a `&[u8]` slice without copying.
//!
//! - [`MemBioBuf<'a>`] — a read-only view of a caller-supplied slice
//!   (`BIO_new_mem_buf()`).  Zero-copy input path for PEM parsing.
//!
//! - [`Bio`] — shared ownership wrapper around a raw `BIO*`.  Used when OpenSSL
//!   needs a `BIO` that outlives the immediate call (e.g. TLS `SSL_set_bio`).
//!   Supports read/write I/O and BIO chain operations.
//!
//! - [`BorrowedBio<'_>`] — a non-owning view of a `BIO*` returned by chain
//!   walk operations (`BIO_next`, `BIO_find_type`).  Does **not** free on drop.

use crate::error::ErrorStack;
use native_ossl_sys as sys;
use std::marker::PhantomData;
use std::mem::ManuallyDrop;
use std::os::raw::c_int;
use std::ptr;

// ── MemBio — writable in-memory BIO ──────────────────────────────────────────

/// A writable, growable in-memory BIO.
///
/// Data written to this BIO accumulates in an internal buffer managed by
/// OpenSSL.  After writing, `data()` returns a borrowed slice without copying.
pub struct MemBio {
    ptr: *mut sys::BIO,
}

impl MemBio {
    /// Create a new empty writable `BIO_s_mem()` BIO.
    ///
    /// # Errors
    ///
    /// Returns `Err` if OpenSSL cannot allocate the BIO.
    pub fn new() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::BIO_s_mem() };
        if method.is_null() {
            return Err(ErrorStack::drain());
        }
        let ptr = unsafe { sys::BIO_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(MemBio { ptr })
    }

    /// Write bytes into the BIO's internal buffer.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the write fails.
    pub fn write(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
        let mut written: usize = 0;
        let rc = unsafe {
            sys::BIO_write_ex(
                self.ptr,
                data.as_ptr().cast(),
                data.len(),
                std::ptr::addr_of_mut!(written),
            )
        };
        if rc != 1 || written != data.len() {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Borrow the current contents of the BIO's buffer as a `&[u8]`.
    ///
    /// The slice is valid until the next write operation or until `self` is dropped.
    /// This is a zero-copy view — no allocation occurs.
    #[must_use]
    pub fn data(&self) -> &[u8] {
        let mut ptr: *mut std::os::raw::c_char = ptr::null_mut();
        // BIO_get_mem_data is the C macro equivalent of:
        //   BIO_ctrl(b, BIO_CTRL_INFO, 0, (char**)(pp))
        // BIO_CTRL_INFO = 3.
        let len = unsafe {
            sys::BIO_ctrl(
                self.ptr,
                3, // BIO_CTRL_INFO
                0,
                (&raw mut ptr).cast::<std::os::raw::c_void>(),
            )
        };
        if len <= 0 || ptr.is_null() {
            return &[];
        }
        let n = usize::try_from(len).unwrap_or(0);
        unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), n) }
    }

    /// Move the buffer contents into a freshly allocated `Vec<u8>`.
    ///
    /// Prefer `data()` when a borrow suffices.
    #[must_use]
    pub fn into_vec(self) -> Vec<u8> {
        self.data().to_vec()
    }

    /// Return the raw `BIO*` pointer.
    ///
    /// The pointer is valid for the lifetime of `self`.
    #[must_use]
    #[allow(dead_code)] // used by x509/ssl modules added in Phase 7-8
    pub(crate) fn as_ptr(&mut self) -> *mut sys::BIO {
        self.ptr
    }
}

impl Drop for MemBio {
    fn drop(&mut self) {
        unsafe { sys::BIO_free_all(self.ptr) };
    }
}

// SAFETY: BIO_s_mem() BIOs do not reference external state.
unsafe impl Send for MemBio {}

// ── MemBioBuf — read-only view into a caller slice ───────────────────────────

/// A read-only BIO wrapping a borrowed byte slice (`BIO_new_mem_buf()`).
///
/// Zero-copy: no data is copied from the slice.  The `BIO*` pointer reads
/// directly from the caller's memory.  The lifetime `'a` ties the BIO to the
/// source slice.
pub struct MemBioBuf<'a> {
    ptr: *mut sys::BIO,
    _data: PhantomData<&'a [u8]>,
}

impl<'a> MemBioBuf<'a> {
    /// Create a read-only BIO backed by `data`.
    ///
    /// OpenSSL reads from `data` directly; no copy occurs.
    ///
    /// # Errors
    ///
    /// Returns `Err` if OpenSSL cannot allocate the BIO wrapper, or if
    /// `data.len()` exceeds `i32::MAX`.
    pub fn new(data: &'a [u8]) -> Result<Self, ErrorStack> {
        // BIO_new_mem_buf reads from the caller's slice directly.
        // -1 means use data.len() (NUL-terminated string convention is not used here
        // because we pass the explicit length).
        let len = i32::try_from(data.len()).map_err(|_| ErrorStack::drain())?;
        let ptr = unsafe { sys::BIO_new_mem_buf(data.as_ptr().cast(), len) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(MemBioBuf {
            ptr,
            _data: PhantomData,
        })
    }

    /// Return the raw `BIO*` pointer.
    #[must_use]
    #[allow(dead_code)] // used by x509/ssl modules added in Phase 7-8
    pub(crate) fn as_ptr(&self) -> *mut sys::BIO {
        self.ptr
    }
}

impl Drop for MemBioBuf<'_> {
    fn drop(&mut self) {
        unsafe { sys::BIO_free(self.ptr) };
    }
}

// SAFETY: the slice reference `'a` bounds the BIO's use; it cannot outlive the slice.
unsafe impl Send for MemBioBuf<'_> {}

// ── Bio — shared ownership BIO ────────────────────────────────────────────────

/// Shared ownership wrapper around a `BIO*`.
///
/// Used where OpenSSL takes ownership of a BIO (e.g. `SSL_set_bio`) or where
/// the same BIO must be reachable from multiple Rust values.  Implemented with
/// `BIO_up_ref` / `BIO_free`.
pub struct Bio {
    ptr: *mut sys::BIO,
}

impl Bio {
    /// Create a linked in-memory BIO pair suitable for in-process TLS.
    ///
    /// Returns `(bio1, bio2)` where data written to `bio1` is readable from
    /// `bio2` and vice-versa.  Pass each half to [`crate::ssl::Ssl::set_bio_duplex`] on
    /// the client and server `Ssl` objects respectively.
    ///
    /// # Errors
    ///
    /// Returns `Err` if OpenSSL fails to allocate the pair.
    pub fn new_pair() -> Result<(Self, Self), crate::error::ErrorStack> {
        let mut b1: *mut sys::BIO = std::ptr::null_mut();
        let mut b2: *mut sys::BIO = std::ptr::null_mut();
        let rc = unsafe {
            sys::BIO_new_bio_pair(std::ptr::addr_of_mut!(b1), 0, std::ptr::addr_of_mut!(b2), 0)
        };
        if rc != 1 {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok((Bio { ptr: b1 }, Bio { ptr: b2 }))
    }

    /// Wrap a raw `BIO*` transferring ownership to this `Bio`.
    ///
    /// # Safety
    ///
    /// `ptr` must be a valid, non-null `BIO*` that the caller is giving up ownership of.
    #[must_use]
    #[allow(dead_code)] // used by ssl tests
    pub(crate) unsafe fn from_ptr_owned(ptr: *mut sys::BIO) -> Self {
        Bio { ptr }
    }

    /// Return the raw `BIO*` pointer.  Valid for the lifetime of `self`.
    #[must_use]
    pub(crate) fn as_ptr(&self) -> *mut sys::BIO {
        self.ptr
    }

    // ── I/O methods ──────────────────────────────────────────────────────────

    /// Read bytes from the BIO into `buf`.
    ///
    /// Returns the number of bytes actually read, which may be less than
    /// `buf.len()` if fewer bytes are available.
    ///
    /// # Errors
    ///
    /// Returns `Err` on I/O error or EOF (when `BIO_read` returns -1).
    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, ErrorStack> {
        let len = i32::try_from(buf.len()).unwrap_or(i32::MAX);
        // SAFETY: `self.ptr` is a valid, non-null BIO*; `buf` is a live mutable slice.
        let n = unsafe { sys::BIO_read(self.ptr, buf.as_mut_ptr().cast(), len) };
        if n < 0 {
            return Err(ErrorStack::drain());
        }
        // n >= 0 is guaranteed by the check above; unwrap is infallible.
        Ok(usize::try_from(n).unwrap_or(0))
    }

    /// Read bytes from the BIO, reporting the exact number of bytes read.
    ///
    /// On success returns the number of bytes placed into `buf`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `BIO_read_ex` reports failure (returns 0).
    pub fn read_ex(&mut self, buf: &mut [u8]) -> Result<usize, ErrorStack> {
        let mut readbytes: usize = 0;
        // SAFETY: `self.ptr` is valid; `buf` is a live mutable slice; `readbytes` is a
        //         live stack variable whose address remains valid for the duration of the call.
        crate::ossl_call!(sys::BIO_read_ex(
            self.ptr,
            buf.as_mut_ptr().cast(),
            buf.len(),
            &raw mut readbytes
        ))?;
        Ok(readbytes)
    }

    /// Write `buf` into the BIO.
    ///
    /// Returns the number of bytes actually written.
    ///
    /// # Errors
    ///
    /// Returns `Err` on I/O error (when `BIO_write` returns -1).
    pub fn write(&mut self, buf: &[u8]) -> Result<usize, ErrorStack> {
        let len = i32::try_from(buf.len()).unwrap_or(i32::MAX);
        // SAFETY: `self.ptr` is a valid, non-null BIO*; `buf` is a live immutable slice.
        let n = unsafe { sys::BIO_write(self.ptr, buf.as_ptr().cast(), len) };
        if n < 0 {
            return Err(ErrorStack::drain());
        }
        // n >= 0 is guaranteed by the check above; unwrap is infallible.
        Ok(usize::try_from(n).unwrap_or(0))
    }

    // ── Chain management ─────────────────────────────────────────────────────

    /// Append `next` after `self` in the BIO chain.
    ///
    /// Ownership of `next` is transferred into the chain; it must **not** be
    /// freed separately.  Returns `self` with the chain extended.
    ///
    /// Corresponds to `BIO_push(self, next)`.
    #[must_use]
    pub fn push(self, next: Bio) -> Bio {
        // Prevent Rust from calling BIO_free on `next` — the chain now owns it.
        let next_raw = ManuallyDrop::new(next).ptr;
        // Prevent Rust from calling BIO_free on `self` — we return the new owner.
        let self_raw = ManuallyDrop::new(self).ptr;
        // SAFETY: both pointers are valid, non-null BIO*s.  BIO_push transfers
        //         ownership of `next_raw` into the chain headed by `self_raw`.
        let result = unsafe { sys::BIO_push(self_raw, next_raw) };
        // BIO_push always returns its first argument; wrap it as the owning Bio.
        Bio { ptr: result }
    }

    /// Remove `self` from its chain and return the rest of the chain.
    ///
    /// After this call `self` is a standalone BIO.  Returns `None` if `self`
    /// was the only (or last) element in the chain.
    ///
    /// Corresponds to `BIO_pop(self)`.
    #[must_use]
    pub fn pop(&mut self) -> Option<Bio> {
        // SAFETY: `self.ptr` is a valid, non-null BIO*.  BIO_pop detaches `self`
        //         from the chain and returns the next BIO (now an independent owner).
        let next = unsafe { sys::BIO_pop(self.ptr) };
        if next.is_null() {
            None
        } else {
            Some(Bio { ptr: next })
        }
    }

    /// Return a borrowed view of the next BIO in the chain without consuming `self`.
    ///
    /// The returned [`BorrowedBio`] is valid for the lifetime of `self` and does
    /// **not** free the underlying pointer when dropped.
    ///
    /// Returns `None` if `self` is the last (or only) BIO in the chain.
    ///
    /// Corresponds to `BIO_next(self)`.
    #[must_use]
    pub fn next(&self) -> Option<BorrowedBio<'_>> {
        // SAFETY: `self.ptr` is a valid, non-null BIO*.  BIO_next returns a borrowed
        //         pointer whose lifetime is tied to the chain — expressed here via `'_`.
        let next = unsafe { sys::BIO_next(self.ptr) };
        if next.is_null() {
            None
        } else {
            Some(BorrowedBio {
                inner: ManuallyDrop::new(Bio { ptr: next }),
                _marker: PhantomData,
            })
        }
    }

    /// Return the number of bytes available for reading from the BIO.
    ///
    /// Corresponds to the `BIO_pending` C macro, implemented via
    /// `BIO_ctrl(b, BIO_CTRL_PENDING, 0, NULL)`.
    ///
    /// For a mem BIO this is the number of unread bytes in the buffer.
    /// For other BIO types the value is type-specific.
    #[must_use]
    pub fn pending(&self) -> usize {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant; never stores null)
        // - BIO_ctrl with BIO_CTRL_PENDING=10 is a read-only query; &self is sufficient
        // - no aliasing concern: &self ensures no concurrent mutation
        let n = unsafe {
            sys::BIO_ctrl(
                self.ptr,
                10, // BIO_CTRL_PENDING
                0,
                std::ptr::null_mut(),
            )
        };
        usize::try_from(n).unwrap_or(0)
    }

    /// Return the number of bytes still to be written (pending in the write buffer).
    ///
    /// Corresponds to the `BIO_wpending` C macro, implemented via
    /// `BIO_ctrl(b, BIO_CTRL_WPENDING, 0, NULL)`.
    ///
    /// For most BIO types this is 0 (writes are synchronous). For filter
    /// BIOs it reflects bytes buffered but not yet flushed downstream.
    #[must_use]
    pub fn wpending(&self) -> usize {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant; never stores null)
        // - BIO_ctrl with BIO_CTRL_WPENDING=13 is a read-only query; &self is sufficient
        // - no aliasing concern: &self ensures no concurrent mutation
        let n = unsafe {
            sys::BIO_ctrl(
                self.ptr,
                13, // BIO_CTRL_WPENDING
                0,
                std::ptr::null_mut(),
            )
        };
        usize::try_from(n).unwrap_or(0)
    }

    /// Search the chain for the first BIO of the given `bio_type`.
    ///
    /// `bio_type` is one of the `BIO_TYPE_*` integer constants from OpenSSL
    /// (e.g. `BIO_TYPE_MEM = 8`, `BIO_TYPE_BIO = 19`).
    ///
    /// Returns a borrowed view of the matching BIO, or `None` if none is found.
    ///
    /// Corresponds to `BIO_find_type(self, bio_type)`.
    ///
    /// # Errors
    ///
    /// Returns `None` if no BIO of the requested type exists in the chain.
    #[must_use]
    pub fn find_type(&self, bio_type: c_int) -> Option<BorrowedBio<'_>> {
        // SAFETY: `self.ptr` is a valid, non-null BIO*; `bio_type` is a plain integer.
        //         The returned pointer is borrowed from the chain; lifetime tied to `self`.
        let found = unsafe { sys::BIO_find_type(self.ptr, bio_type) };
        if found.is_null() {
            None
        } else {
            Some(BorrowedBio {
                inner: ManuallyDrop::new(Bio { ptr: found }),
                _marker: PhantomData,
            })
        }
    }
}

impl Clone for Bio {
    fn clone(&self) -> Self {
        unsafe { sys::BIO_up_ref(self.ptr) };
        Bio { ptr: self.ptr }
    }
}

impl Drop for Bio {
    fn drop(&mut self) {
        unsafe { sys::BIO_free(self.ptr) };
    }
}

// SAFETY: `BIO_up_ref` / `BIO_free` are thread-safe for memory BIOs.
unsafe impl Send for Bio {}
unsafe impl Sync for Bio {}

// ── BorrowedBio — non-owning chain view ──────────────────────────────────────

/// A non-owning view of a `BIO*` returned by chain walk operations.
///
/// Obtained from [`Bio::next`] or [`Bio::find_type`].  The underlying pointer
/// is **not** freed when this value is dropped — ownership remains with the
/// chain.
///
/// The lifetime `'a` is tied to the [`Bio`] that produced this value, ensuring
/// the borrowed pointer cannot outlive the chain it belongs to.
pub struct BorrowedBio<'a> {
    /// Wrap in `ManuallyDrop` so the `Bio` destructor is never called.
    inner: ManuallyDrop<Bio>,
    _marker: PhantomData<&'a Bio>,
}

impl BorrowedBio<'_> {
    /// Return the raw `BIO*` pointer.
    ///
    /// The pointer is valid for the lifetime of the `Bio` chain this was
    /// borrowed from.
    #[must_use]
    pub fn as_ptr(&self) -> *mut sys::BIO {
        self.inner.ptr
    }
}

// SAFETY: no destructor — ManuallyDrop prevents BIO_free being called.
// The lifetime `'a` statically prevents the borrowed BIO* from outliving its chain.
unsafe impl Send for BorrowedBio<'_> {}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mem_bio_write_and_data() {
        let mut bio = MemBio::new().unwrap();
        bio.write(b"hello").unwrap();
        bio.write(b" world").unwrap();
        assert_eq!(bio.data(), b"hello world");
    }

    #[test]
    fn mem_bio_empty() {
        let bio = MemBio::new().unwrap();
        assert_eq!(bio.data(), b"");
    }

    #[test]
    fn mem_bio_buf_zero_copy() {
        let source = b"PEM data goes here";
        let bio = MemBioBuf::new(source).unwrap();
        // Verify the BIO's internal read pointer equals the source slice pointer.
        let mut char_ptr: *mut std::os::raw::c_char = ptr::null_mut();
        // BIO_get_mem_data via BIO_ctrl(BIO_CTRL_INFO=3).
        let len = unsafe {
            sys::BIO_ctrl(
                bio.as_ptr(),
                3, // BIO_CTRL_INFO
                0,
                (&raw mut char_ptr).cast::<std::os::raw::c_void>(),
            )
        };
        assert_eq!(usize::try_from(len).unwrap(), source.len());
        // The data pointer must be the same as the source slice's pointer.
        assert_eq!(char_ptr.cast::<u8>().cast_const(), source.as_ptr());
    }

    #[test]
    fn bio_clone_shares_object() {
        // Create a MemBio and wrap its underlying pointer in a Bio to test Clone.
        let mut mem = MemBio::new().unwrap();
        mem.write(b"test").unwrap();

        // Build a Bio using the MemBio's pointer (up_ref first to share ownership).
        let raw = mem.as_ptr();
        unsafe { sys::BIO_up_ref(raw) };
        let bio = unsafe { Bio::from_ptr_owned(raw) };
        let bio2 = bio.clone();

        // Both should point to the same BIO object.
        assert_eq!(bio.as_ptr(), bio2.as_ptr());
    }

    #[test]
    fn bio_mem_write_then_read() {
        // Create a mem BIO, write bytes, read them back via Bio::read.
        // SAFETY: BIO_new / BIO_s_mem are the canonical way to create a mem BIO.
        let raw = unsafe { sys::BIO_new(sys::BIO_s_mem()) };
        assert!(!raw.is_null());
        let mut bio = unsafe { Bio::from_ptr_owned(raw) };

        let payload = b"hello, BIO";
        let written = bio.write(payload).unwrap();
        assert_eq!(written, payload.len());

        let mut buf = [0u8; 64];
        let nread = bio.read(&mut buf).unwrap();
        assert_eq!(nread, payload.len());
        assert_eq!(&buf[..nread], payload);
    }

    #[test]
    fn bio_read_ex() {
        // Same as bio_mem_write_then_read but uses read_ex to verify readbytes count.
        // SAFETY: BIO_new / BIO_s_mem create a valid mem BIO.
        let raw = unsafe { sys::BIO_new(sys::BIO_s_mem()) };
        assert!(!raw.is_null());
        let mut bio = unsafe { Bio::from_ptr_owned(raw) };

        let payload = b"read_ex test";
        bio.write(payload).unwrap();

        let mut buf = [0u8; 64];
        let nread = bio.read_ex(&mut buf).unwrap();
        assert_eq!(nread, payload.len());
        assert_eq!(&buf[..nread], payload);
    }

    #[test]
    fn bio_pending_on_mem_bio() {
        // Write known bytes into a mem BIO; pending() must return the byte count.
        // SAFETY: BIO_new / BIO_s_mem create a valid mem BIO.
        let raw = unsafe { sys::BIO_new(sys::BIO_s_mem()) };
        assert!(!raw.is_null());
        let mut bio = unsafe { Bio::from_ptr_owned(raw) };

        let payload = b"hello, pending";
        bio.write(payload).unwrap();

        // pending() should equal the number of unread bytes.
        assert_eq!(bio.pending(), payload.len());

        // wpending() for a mem BIO is 0 (synchronous writes).
        assert_eq!(bio.wpending(), 0);

        // After reading all bytes, pending() should drop to 0.
        let mut buf = vec![0u8; payload.len()];
        let n = bio.read(&mut buf).unwrap();
        assert_eq!(n, payload.len());
        assert_eq!(bio.pending(), 0);
    }

    #[test]
    fn bio_chain_push_next_pop() {
        // Create two independent mem BIOs, push one after the other, verify
        // next() sees the second, then pop() detaches it.
        // SAFETY: BIO_new / BIO_s_mem create valid mem BIOs.
        let ptr1 = unsafe { sys::BIO_new(sys::BIO_s_mem()) };
        let ptr2 = unsafe { sys::BIO_new(sys::BIO_s_mem()) };
        assert!(!ptr1.is_null());
        assert!(!ptr2.is_null());

        let bio1 = unsafe { Bio::from_ptr_owned(ptr1) };
        let bio2 = unsafe { Bio::from_ptr_owned(ptr2) };

        // Remember ptr2 before ownership moves into the chain.
        let raw2 = bio2.as_ptr();

        // Push bio2 after bio1; bio1 takes ownership of the chain.
        let mut chain = bio1.push(bio2);

        // next() should return a borrowed view of bio2.
        {
            let next = chain.next().expect("chain must have a next BIO");
            assert_eq!(next.as_ptr(), raw2);
        } // `next` (BorrowedBio) goes out of scope here, releasing the borrow.

        // pop() detaches chain from its successor; returns Some(bio2_owner).
        let detached = chain.pop().expect("pop must return the detached tail");
        assert_eq!(detached.as_ptr(), raw2);

        // After pop, chain has no successor.
        assert!(chain.next().is_none());
    }
}
