//! TLS — `SSL_CTX`, `SSL`, and `SSL_SESSION` wrappers.
//!
//! # Types
//!
//! | Type           | Owned / Shared | Description                               |
//! |----------------|---------------|-------------------------------------------|
//! | [`SslCtx`]     | Shared (Clone) | TLS context — configuration, certs, keys  |
//! | [`Ssl`]        | Exclusive      | Per-connection TLS object                 |
//! | [`SslSession`] | Shared (Clone) | Resumable TLS session handle              |
//!
//! # Protocol version
//!
//! `SSL_CTX_set_min_proto_version` / `SSL_CTX_set_max_proto_version` are C macros
//! that expand to `SSL_CTX_ctrl(ctx, 123 / 124, version, NULL)`.  This module
//! calls `SSL_CTX_ctrl` directly since bindgen cannot expose C macros as functions.
//!
//! # SNI hostname
//!
//! `SSL_set_tlsext_host_name` is a C macro expanding to
//! `SSL_ctrl(s, 55, 0, name)`.  Use [`Ssl::set_hostname`] to set the SNI extension.
//!
//! # BIO ownership
//!
//! `SSL_set_bio` transfers ownership of the supplied `BIO*` pointers to the `SSL`
//! object.  [`Ssl::set_bio_duplex`] accepts a single [`crate::bio::Bio`] for the
//! common case where the same BIO serves as both read and write channel (e.g. the
//! output of `BIO_new_bio_pair`).

use crate::bio::Bio;
use crate::error::ErrorStack;
use crate::pkey::{HasPrivate, Pkey};
use crate::x509::X509;
use native_ossl_sys as sys;
use std::ffi::CStr;
use std::mem::ManuallyDrop;

// ── Role sealed trait and markers ─────────────────────────────────────────────

mod private {
    pub trait Sealed {}
}

/// Marker for a TLS server context builder.
pub struct Server;
/// Marker for a TLS client context builder.
pub struct Client;

/// Role marker for [`SslCtxBuilder`]. Sealed — cannot be implemented externally.
pub trait Role: private::Sealed {}

impl private::Sealed for Server {}
impl private::Sealed for Client {}
impl Role for Server {}
impl Role for Client {}

// ── TLS version ───────────────────────────────────────────────────────────────

/// TLS protocol version selector.
///
/// Passed to [`SslCtx::set_min_proto_version`] and [`SslCtx::set_max_proto_version`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TlsVersion {
    /// TLS 1.2 (`0x0303`)
    Tls12 = 0x0303,
    /// TLS 1.3 (`0x0304`)
    Tls13 = 0x0304,
}

// ── Verify mode ───────────────────────────────────────────────────────────────

/// Certificate verification mode flags.
///
/// Combine with bitwise OR using [`SslVerifyMode::or`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SslVerifyMode(i32);

impl SslVerifyMode {
    /// Do not verify the peer certificate (`SSL_VERIFY_NONE`).
    pub const NONE: Self = SslVerifyMode(0x00);
    /// Verify the peer certificate (`SSL_VERIFY_PEER`).
    pub const PEER: Self = SslVerifyMode(0x01);
    /// Fail if the peer does not present a certificate (`SSL_VERIFY_FAIL_IF_NO_PEER_CERT`).
    pub const FAIL_IF_NO_PEER_CERT: Self = SslVerifyMode(0x02);

    /// Combine two mode values with bitwise OR.
    #[must_use]
    pub fn or(self, other: Self) -> Self {
        SslVerifyMode(self.0 | other.0)
    }
}

// ── Hostname verification flags ───────────────────────────────────────────────

/// Flags controlling hostname verification behaviour during certificate checks.
///
/// Passed to [`SslCtxBuilder::<Client>::verify_hostname_flags`].
/// Combine flags using [`HostnameFlags::or`].
///
/// These map directly to the `X509_CHECK_FLAG_*` constants defined in
/// `<openssl/x509_vfy.h>`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct HostnameFlags(u32);

impl HostnameFlags {
    /// No special hostname flags (default OpenSSL behaviour).
    pub const NONE: Self = Self(0);

    /// Disallow partial wildcards like `*.example.*` — only single trailing-label
    /// wildcards such as `*.example.com` are permitted.
    ///
    /// Maps to `X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS` (`0x4`).
    pub const NO_PARTIAL_WILDCARDS: Self = Self(0x4);

    /// Allow wildcards to match multiple labels (e.g. `*.foo.example.com`).
    ///
    /// Maps to `X509_CHECK_FLAG_MULTI_LABEL_WILDCARDS` (`0x8`).
    pub const MULTI_LABEL_WILDCARDS: Self = Self(0x8);

    /// Combine two flag values with bitwise OR.
    #[must_use]
    pub fn or(self, other: Self) -> Self {
        Self(self.0 | other.0)
    }
}

// ── SSL I/O error ─────────────────────────────────────────────────────────────

/// Error returned by non-blocking SSL I/O operations.
///
/// `WantRead` / `WantWrite` indicate that the operation should be retried
/// after the underlying BIO becomes ready.  For in-memory BIO pairs, retrying
/// immediately after driving the peer is sufficient.
#[derive(Debug)]
pub enum SslIoError {
    /// Retry after data arrives on the read BIO (`SSL_ERROR_WANT_READ`).
    WantRead,
    /// Retry after the write BIO drains (`SSL_ERROR_WANT_WRITE`).
    WantWrite,
    /// The peer closed the connection cleanly (`SSL_ERROR_ZERO_RETURN`).
    ZeroReturn,
    /// Underlying I/O error; see [`ErrorStack`] for details (`SSL_ERROR_SYSCALL`).
    Syscall(ErrorStack),
    /// OpenSSL protocol error (`SSL_ERROR_SSL`).
    Ssl(ErrorStack),
    /// Unexpected error code returned by `SSL_get_error`.
    Other(i32),
}

impl std::fmt::Display for SslIoError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::WantRead => write!(f, "SSL want read"),
            Self::WantWrite => write!(f, "SSL want write"),
            Self::ZeroReturn => write!(f, "SSL zero return (peer closed)"),
            Self::Syscall(e) => write!(f, "SSL syscall error: {e}"),
            Self::Ssl(e) => write!(f, "SSL error: {e}"),
            Self::Other(code) => write!(f, "SSL error code {code}"),
        }
    }
}

impl std::error::Error for SslIoError {}

// ── ShutdownResult ────────────────────────────────────────────────────────────

/// Result of [`Ssl::shutdown`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShutdownResult {
    /// First shutdown stage sent; call `shutdown` again to complete.
    Sent,
    /// Bidirectional shutdown complete.
    Complete,
}

// ── SslSession ────────────────────────────────────────────────────────────────

/// A TLS session handle (`SSL_SESSION*`).
///
/// Cloneable via `SSL_SESSION_up_ref`.  Pass to [`Ssl::set_session`] to enable
/// session resumption.
pub struct SslSession {
    ptr: *mut sys::SSL_SESSION,
}

// SAFETY: `SSL_SESSION` is reference-counted.
unsafe impl Send for SslSession {}
unsafe impl Sync for SslSession {}

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

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

// ── BorrowedSslSession ────────────────────────────────────────────────────────

/// A borrowed view of the current TLS session (`SSL_SESSION*`), tied to the
/// lifetime of the owning [`Ssl`] connection.
///
/// Obtained from [`Ssl::session`].  The pointer is **not** freed on drop —
/// OpenSSL owns it for as long as the `SSL` object is alive.  Use
/// [`Ssl::get1_session`] if you need an independent owned copy.
///
/// All read-only methods of [`SslSession`] are available via `Deref`.
pub struct BorrowedSslSession<'ssl> {
    inner: ManuallyDrop<SslSession>,
    _marker: std::marker::PhantomData<&'ssl Ssl>,
}

// SAFETY: `SSL_SESSION*` is reference-counted and safe to move between threads.
// `BorrowedSslSession` does not free the pointer, so moving it is at most as
// permissive as moving the reference that backs it.
unsafe impl Send for BorrowedSslSession<'_> {}

impl std::ops::Deref for BorrowedSslSession<'_> {
    type Target = SslSession;

    fn deref(&self) -> &SslSession {
        &self.inner
    }
}

// ── SslCtx ────────────────────────────────────────────────────────────────────

/// TLS context (`SSL_CTX*`).
///
/// Holds shared configuration such as certificates, private keys, and verify
/// settings.  Multiple [`Ssl`] objects can be created from the same `SslCtx`.
///
/// Cloneable via `SSL_CTX_up_ref`; wrapping in `Arc<SslCtx>` is safe.
pub struct SslCtx {
    ptr: *mut sys::SSL_CTX,
}

// SAFETY: `SSL_CTX` is reference-counted.
unsafe impl Send for SslCtx {}
unsafe impl Sync for SslCtx {}

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

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

impl SslCtx {
    /// Create a new TLS context accepting any role (client or server).
    ///
    /// Uses the generic `TLS_method()`.  Call [`SslCtx::new_client`] or
    /// [`SslCtx::new_server`] for role-specific method selection.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_CTX_new` fails.
    pub fn new() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::TLS_method() };
        let ptr = unsafe { sys::SSL_CTX_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SslCtx { ptr })
    }

    /// Create a new TLS context optimised for client connections (`TLS_client_method`).
    ///
    /// # Errors
    pub fn new_client() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::TLS_client_method() };
        let ptr = unsafe { sys::SSL_CTX_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SslCtx { ptr })
    }

    /// Create a new TLS context optimised for server connections (`TLS_server_method`).
    ///
    /// # Errors
    pub fn new_server() -> Result<Self, ErrorStack> {
        let method = unsafe { sys::TLS_server_method() };
        let ptr = unsafe { sys::SSL_CTX_new(method) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(SslCtx { ptr })
    }

    /// Set the minimum acceptable TLS protocol version.
    ///
    /// Internally calls `SSL_CTX_ctrl(ctx, 123 /*SSL_CTRL_SET_MIN_PROTO_VERSION*/, version, NULL)`.
    ///
    /// # Errors
    pub fn set_min_proto_version(&self, ver: TlsVersion) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_ctrl(self.ptr, 123, ver as i64, std::ptr::null_mut()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set the maximum acceptable TLS protocol version.
    ///
    /// Internally calls `SSL_CTX_ctrl(ctx, 124 /*SSL_CTRL_SET_MAX_PROTO_VERSION*/, version, NULL)`.
    ///
    /// # Errors
    pub fn set_max_proto_version(&self, ver: TlsVersion) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_ctrl(self.ptr, 124, ver as i64, std::ptr::null_mut()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set the peer certificate verification mode.
    ///
    /// Wraps `SSL_CTX_set_verify(ctx, mode, NULL)`.
    pub fn set_verify(&self, mode: SslVerifyMode) {
        unsafe { sys::SSL_CTX_set_verify(self.ptr, mode.0, None) };
    }

    /// Set the allowed cipher list (TLS 1.2 and below).
    ///
    /// `list` uses OpenSSL cipher string syntax (e.g. `c"HIGH:!aNULL:!MD5"`).
    ///
    /// # Errors
    pub fn set_cipher_list(&self, list: &CStr) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_set_cipher_list(self.ptr, list.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set the allowed TLS 1.3 ciphersuites.
    ///
    /// `list` uses OpenSSL ciphersuite syntax (e.g. `c"TLS_AES_256_GCM_SHA384"`).
    ///
    /// # Errors
    pub fn set_ciphersuites(&self, list: &CStr) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_set_ciphersuites(self.ptr, list.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Load a certificate into the context.
    ///
    /// For a server, this is the certificate that will be presented to clients.
    ///
    /// # Errors
    pub fn use_certificate(&self, cert: &X509) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_use_certificate(self.ptr, cert.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Load a private key into the context.
    ///
    /// The key must correspond to the certificate loaded via [`SslCtx::use_certificate`].
    ///
    /// # Errors
    pub fn use_private_key<T: HasPrivate>(&self, key: &Pkey<T>) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_use_PrivateKey(self.ptr, key.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Verify that the loaded certificate and private key are consistent.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the key/certificate pair is invalid or not loaded.
    pub fn check_private_key(&self) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_check_private_key(self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Load the system default CA certificate store for verification.
    ///
    /// # Errors
    pub fn set_default_verify_paths(&self) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_CTX_set_default_verify_paths(self.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Disable TLS session caching on this context.
    pub fn disable_session_cache(&self) {
        // SSL_CTRL_SET_SESS_CACHE_MODE = 44, SSL_SESS_CACHE_OFF = 0
        unsafe { sys::SSL_CTX_ctrl(self.ptr, 44, 0, std::ptr::null_mut()) };
    }

    /// Create a new [`Ssl`] connection object from this context.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_new` fails.
    pub fn new_ssl(&self) -> Result<Ssl, ErrorStack> {
        let ptr = unsafe { sys::SSL_new(self.ptr) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Ssl { ptr })
    }
}

// ── SslCtxBuilder ─────────────────────────────────────────────────────────────

/// Fluent typestate builder for [`SslCtx`].
///
/// Use [`SslCtxBuilder::<Server>::new()`] or [`SslCtxBuilder::<Client>::new()`] to
/// start the builder chain, configure via consuming methods, then call
/// [`build()`][SslCtxBuilder::build] to obtain a fully-configured [`SslCtx`].
///
/// # Role separation
///
/// The type parameter `R` (either [`Server`] or [`Client`]) ensures that
/// role-specific methods — e.g. [`certificate`][SslCtxBuilder::certificate] for
/// servers, [`verify_peer`][SslCtxBuilder::verify_peer] for clients — are only
/// available on the appropriate builder type.
///
/// # Example — server
///
/// ```rust,ignore
/// let ctx = SslCtxBuilder::<Server>::new()?
///     .certificate(&cert)?
///     .private_key(&key)?
///     .check_private_key()?
///     .build()?;
/// ```
///
/// # Example — client
///
/// ```rust,ignore
/// let ctx = SslCtxBuilder::<Client>::new()?
///     .default_ca_paths()?
///     .verify_peer()
///     .verify_hostname("example.com")?
///     .alpn_protos_list(&["h2", "http/1.1"])?
///     .build()?;
/// ```
// NOTE: SslCtxBuilder is Send but !Sync.
// Send: we hold exclusive ownership of the SSL_CTX* during construction; moving
// it to another thread is safe because no other thread can reference the pointer.
// !Sync: sharing a &SslCtxBuilder across threads would allow concurrent mutation
// through the raw pointer, which is not safe.  No Arc is used here intentionally.
#[must_use = "call .build() to obtain an SslCtx"]
pub struct SslCtxBuilder<R: Role> {
    ptr: *mut sys::SSL_CTX,
    _role: std::marker::PhantomData<R>,
}

// SAFETY: SslCtxBuilder holds an SSL_CTX* with exclusive ownership during
// construction.  No other reference to the pointer exists until build() is
// called.  Moving the builder between threads is therefore safe.
unsafe impl<R: Role> Send for SslCtxBuilder<R> {}

impl<R: Role> Drop for SslCtxBuilder<R> {
    fn drop(&mut self) {
        // SAFETY: ptr is non-null by constructor invariant; SSL_CTX_free is safe
        // to call exactly once on a valid pointer.  build() uses mem::forget to
        // prevent this drop from running when ownership is transferred to SslCtx.
        unsafe { sys::SSL_CTX_free(self.ptr) };
    }
}

// ── Constructors ──────────────────────────────────────────────────────────────

impl SslCtxBuilder<Server> {
    /// Create a new server TLS context builder using `TLS_server_method()`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_CTX_new` fails (e.g. OOM).
    pub fn new() -> Result<Self, ErrorStack> {
        // SAFETY: TLS_server_method() returns a static method pointer; SSL_CTX_new
        // returns null on OOM — checked immediately below.
        let ptr = unsafe { sys::SSL_CTX_new(sys::TLS_server_method()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Self {
            ptr,
            _role: std::marker::PhantomData,
        })
    }
}

impl SslCtxBuilder<Client> {
    /// Create a new client TLS context builder using `TLS_client_method()`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_CTX_new` fails (e.g. OOM).
    pub fn new() -> Result<Self, ErrorStack> {
        // SAFETY: TLS_client_method() returns a static method pointer; SSL_CTX_new
        // returns null on OOM — checked immediately below.
        let ptr = unsafe { sys::SSL_CTX_new(sys::TLS_client_method()) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(Self {
            ptr,
            _role: std::marker::PhantomData,
        })
    }
}

// ── Shared methods (both Server and Client) ───────────────────────────────────

impl<R: Role> SslCtxBuilder<R> {
    /// Set the minimum acceptable TLS protocol version.
    ///
    /// Uses `SSL_CTX_ctrl` with `SSL_CTRL_SET_MIN_PROTO_VERSION` (123).
    ///
    /// # Errors
    pub fn min_proto_version(self, ver: TlsVersion) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; SSL_CTX_ctrl with code 123 sets the minimum
        // protocol version and returns 1 on success.
        let rc = unsafe {
            sys::SSL_CTX_ctrl(
                self.ptr,
                sys::SSL_CTRL_SET_MIN_PROTO_VERSION.cast_signed(),
                ver as i64,
                std::ptr::null_mut(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Set the maximum acceptable TLS protocol version.
    ///
    /// Uses `SSL_CTX_ctrl` with `SSL_CTRL_SET_MAX_PROTO_VERSION` (124).
    ///
    /// # Errors
    pub fn max_proto_version(self, ver: TlsVersion) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; SSL_CTX_ctrl with code 124 sets the maximum
        // protocol version and returns 1 on success.
        let rc = unsafe {
            sys::SSL_CTX_ctrl(
                self.ptr,
                sys::SSL_CTRL_SET_MAX_PROTO_VERSION.cast_signed(),
                ver as i64,
                std::ptr::null_mut(),
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Set the allowed cipher list for TLS 1.2 and below.
    ///
    /// `list` uses OpenSSL cipher string syntax (e.g. `c"HIGH:!aNULL:!MD5"`).
    ///
    /// # Errors
    pub fn cipher_list(self, list: &CStr) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; list is a valid NUL-terminated C string.
        crate::ossl_call!(sys::SSL_CTX_set_cipher_list(self.ptr, list.as_ptr()))?;
        Ok(self)
    }

    /// Set the allowed TLS 1.3 ciphersuites.
    ///
    /// `list` uses OpenSSL ciphersuite syntax (e.g. `c"TLS_AES_256_GCM_SHA384"`).
    ///
    /// # Errors
    pub fn ciphersuites(self, list: &CStr) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; list is a valid NUL-terminated C string.
        crate::ossl_call!(sys::SSL_CTX_set_ciphersuites(self.ptr, list.as_ptr()))?;
        Ok(self)
    }

    /// Configure the session cache mode.
    ///
    /// Pass `0` (`SSL_SESS_CACHE_OFF`) to disable caching.
    pub fn session_cache_mode(self, mode: i64) -> Self {
        // SAFETY: self.ptr is valid; SSL_CTX_ctrl with code 44 sets session
        // cache mode — no error return convention, always succeeds.
        unsafe {
            sys::SSL_CTX_ctrl(
                self.ptr,
                sys::SSL_CTRL_SET_SESS_CACHE_MODE.cast_signed(),
                mode,
                std::ptr::null_mut(),
            )
        };
        self
    }

    /// Set the ALPN protocol list in OpenSSL wire format (length-prefixed bytes).
    ///
    /// Example: `b"\x02h2\x08http/1.1"` advertises H2 then HTTP/1.1.
    ///
    /// For a string-slice convenience, use [`alpn_protos_list`][Self::alpn_protos_list].
    ///
    /// # Errors
    ///
    /// Returns `Err` on failure.
    ///
    /// # Note
    ///
    /// `SSL_CTX_set_alpn_protos` uses an **inverted** return convention:
    /// `0` means success, non-zero means failure (opposite to most OpenSSL functions).
    pub fn alpn_protocols(self, protocols: &[u8]) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; SSL_CTX_set_alpn_protos copies the buffer
        // immediately — the slice does not need to outlive this call.
        let len = u32::try_from(protocols.len()).map_err(|_| ErrorStack::drain())?;
        let ret = unsafe { sys::SSL_CTX_set_alpn_protos(self.ptr, protocols.as_ptr(), len) };
        // NOTE: inverted convention — 0 is success for SSL_CTX_set_alpn_protos.
        if ret != 0 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Set the ALPN protocol list from a slice of protocol name strings.
    ///
    /// Encodes `protos` into the wire-format length-prefixed byte string and
    /// calls `SSL_CTX_set_alpn_protos`.  Protocols longer than 255 bytes are
    /// rejected with `Err`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if any protocol name exceeds 255 bytes or if the underlying
    /// OpenSSL call fails.
    pub fn alpn_protos_list(self, protos: &[&str]) -> Result<Self, ErrorStack> {
        let mut wire = Vec::new();
        for proto in protos {
            let len = u8::try_from(proto.len()).map_err(|_| ErrorStack::drain())?;
            wire.push(len);
            wire.extend_from_slice(proto.as_bytes());
        }
        self.alpn_protocols(&wire)
    }

    /// Consume the builder and return a configured [`SslCtx`].
    ///
    /// # Errors
    ///
    /// Currently infallible, but returns `Result` for API consistency with
    /// potential future validation steps.
    pub fn build(self) -> Result<SslCtx, ErrorStack> {
        let ptr = self.ptr;
        // Prevent Drop from calling SSL_CTX_free — SslCtx takes ownership.
        std::mem::forget(self);
        Ok(SslCtx { ptr })
    }
}

// ── Server-only methods ───────────────────────────────────────────────────────

impl SslCtxBuilder<Server> {
    /// Load a certificate that will be presented to clients during the handshake.
    ///
    /// Wraps `SSL_CTX_use_certificate`.
    ///
    /// # Errors
    pub fn certificate(self, cert: &X509) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; cert.as_ptr() is a valid X509* for the
        // duration of this call; SSL_CTX_use_certificate increments the X509
        // refcount internally — no ownership transfer.
        let rc = unsafe { sys::SSL_CTX_use_certificate(self.ptr, cert.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Append an intermediate CA certificate to the certificate chain.
    ///
    /// Uses `SSL_CTX_ctrl` with `SSL_CTRL_EXTRA_CHAIN_CERT` (14).  OpenSSL
    /// takes ownership of the `X509*`, so this method calls `X509_up_ref` first
    /// to keep `cert` alive after the call.
    ///
    /// # Errors
    pub fn add_chain_cert(self, cert: &X509) -> Result<Self, ErrorStack> {
        // SAFETY: X509_up_ref increments the refcount so that SSL_CTX_ctrl can
        // take ownership of its own reference.  cert.as_ptr() remains valid for
        // the caller because we only transferred the up-ref'd reference.
        unsafe { sys::X509_up_ref(cert.as_ptr()) };
        let rc = unsafe {
            sys::SSL_CTX_ctrl(
                self.ptr,
                sys::SSL_CTRL_EXTRA_CHAIN_CERT.cast_signed(),
                0,
                cert.as_ptr().cast::<std::ffi::c_void>(),
            )
        };
        if rc == 0 {
            // SSL_CTX_ctrl failed; the up-ref'd X509 was not consumed — free it.
            // SAFETY: we up-ref'd above; freeing once is correct.
            unsafe { sys::X509_free(cert.as_ptr()) };
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Load the private key corresponding to the certificate.
    ///
    /// Wraps `SSL_CTX_use_PrivateKey`.
    ///
    /// # Errors
    pub fn private_key(self, key: &Pkey<crate::pkey::Private>) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; key.as_ptr() is a valid EVP_PKEY* for the
        // duration of this call; SSL_CTX_use_PrivateKey increments the key
        // refcount — no ownership transfer.
        let rc = unsafe { sys::SSL_CTX_use_PrivateKey(self.ptr, key.as_ptr()) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Verify that the loaded certificate and private key are consistent.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the key/certificate pair is invalid or not yet loaded.
    pub fn check_private_key(self) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid and has had certificate and key loaded.
        crate::ossl_call!(sys::SSL_CTX_check_private_key(self.ptr))?;
        Ok(self)
    }

    /// Request (or require) a client certificate during the TLS handshake.
    ///
    /// When `require` is `true`, combines `SSL_VERIFY_PEER` with
    /// `SSL_VERIFY_FAIL_IF_NO_PEER_CERT` — the handshake fails if the client
    /// does not present a certificate.  When `false`, only `SSL_VERIFY_PEER` is
    /// set — the client certificate is requested but not mandatory.
    pub fn verify_client(self, require: bool) -> Self {
        let mode = if require {
            (sys::SSL_VERIFY_PEER | sys::SSL_VERIFY_FAIL_IF_NO_PEER_CERT).cast_signed()
        } else {
            sys::SSL_VERIFY_PEER.cast_signed()
        };
        // SAFETY: self.ptr is valid; SSL_CTX_set_verify does not fail.
        unsafe { sys::SSL_CTX_set_verify(self.ptr, mode, None) };
        self
    }
}

// ── Client-only methods ───────────────────────────────────────────────────────

impl SslCtxBuilder<Client> {
    /// Load the system default CA certificate bundle for peer verification.
    ///
    /// Wraps `SSL_CTX_set_default_verify_paths`.
    ///
    /// # Errors
    pub fn default_ca_paths(self) -> Result<Self, ErrorStack> {
        // SAFETY: self.ptr is valid; SSL_CTX_set_default_verify_paths uses the
        // compiled-in default paths and returns 1 on success.
        crate::ossl_call!(sys::SSL_CTX_set_default_verify_paths(self.ptr))?;
        Ok(self)
    }

    /// Load CA certificates from a PEM file at `path`.
    ///
    /// Only the CA certificate file is loaded (not a directory).
    ///
    /// # Errors
    ///
    /// Returns `Err` if the path contains non-UTF-8 bytes, contains a NUL byte,
    /// or if `SSL_CTX_load_verify_locations` fails.
    pub fn ca_bundle_file(self, path: &std::path::Path) -> Result<Self, ErrorStack> {
        let path_cstr = std::ffi::CString::new(path.to_str().ok_or_else(ErrorStack::drain)?)
            .map_err(|_| ErrorStack::drain())?;
        // SAFETY: self.ptr and path_cstr are valid; second arg (cadir) is null
        // because we supply a file only.  SSL_CTX_load_verify_locations returns
        // 1 on success.
        let ret = unsafe {
            sys::SSL_CTX_load_verify_locations(self.ptr, path_cstr.as_ptr(), std::ptr::null())
        };
        if ret != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Add a single CA certificate to the context's trust store.
    ///
    /// Calls `SSL_CTX_get_cert_store` then `X509_STORE_add_cert`.
    /// Does not transfer ownership — `cert` remains valid after this call.
    ///
    /// # Errors
    pub fn ca_cert(self, cert: &X509) -> Result<Self, ErrorStack> {
        // SAFETY: SSL_CTX_get_cert_store returns a borrowed pointer owned by the
        // SSL_CTX — do not free it.  X509_STORE_add_cert does not take ownership
        // of cert; the X509 refcount is unchanged.
        let store = unsafe { sys::SSL_CTX_get_cert_store(self.ptr) };
        if store.is_null() {
            return Err(ErrorStack::drain());
        }
        let ret = unsafe { sys::X509_STORE_add_cert(store, cert.as_ptr()) };
        if ret != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Enable verification of the server's certificate.
    ///
    /// Sets `SSL_VERIFY_PEER`.  Call after loading CA certificates so that
    /// OpenSSL has a trust anchor.
    pub fn verify_peer(self) -> Self {
        // SAFETY: self.ptr is valid; SSL_CTX_set_verify does not fail.
        unsafe { sys::SSL_CTX_set_verify(self.ptr, sys::SSL_VERIFY_PEER.cast_signed(), None) };
        self
    }

    /// Set the expected server hostname for automatic certificate hostname
    /// verification on all connections derived from this context.
    ///
    /// Uses `SSL_CTX_get0_param` to get the context-level `X509_VERIFY_PARAM`
    /// and then `X509_VERIFY_PARAM_set1_host` to set the hostname.  All [`Ssl`]
    /// connections created from this [`SslCtx`] inherit the hostname check.
    ///
    /// For per-connection overrides (e.g. one `SslCtx` shared across connections
    /// to different servers), use [`Ssl::set_verify_hostname`] instead.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `hostname` contains a NUL byte or if
    /// `X509_VERIFY_PARAM_set1_host` fails.
    pub fn verify_hostname(self, hostname: &str) -> Result<Self, ErrorStack> {
        let cstr = std::ffi::CString::new(hostname).map_err(|_| ErrorStack::drain())?;
        // SAFETY: SSL_CTX_get0_param returns a borrowed pointer — do not free.
        // X509_VERIFY_PARAM_set1_host copies the hostname string internally.
        let param = unsafe { sys::SSL_CTX_get0_param(self.ptr) };
        if param.is_null() {
            return Err(ErrorStack::drain());
        }
        let ret = unsafe { sys::X509_VERIFY_PARAM_set1_host(param, cstr.as_ptr(), 0) };
        if ret != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(self)
    }

    /// Set hostname verification flags on this context.
    ///
    /// Applied to every connection derived from this context.  Typically called
    /// after [`verify_hostname`][Self::verify_hostname] to refine how wildcard
    /// certificates are matched.
    ///
    /// Uses `SSL_CTX_get0_param` to obtain the context-level
    /// `X509_VERIFY_PARAM`, then calls `X509_VERIFY_PARAM_set_hostflags`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `SSL_CTX_get0_param` returns a null pointer (should
    /// not happen for a freshly created context).
    pub fn verify_hostname_flags(self, flags: HostnameFlags) -> Result<Self, ErrorStack> {
        // SAFETY: SSL_CTX_get0_param returns a borrowed pointer owned by the
        // SSL_CTX — do not free it.  X509_VERIFY_PARAM_set_hostflags is void
        // and always succeeds.
        let param = unsafe { sys::SSL_CTX_get0_param(self.ptr) };
        if param.is_null() {
            return Err(ErrorStack::drain());
        }
        unsafe { sys::X509_VERIFY_PARAM_set_hostflags(param, flags.0) };
        Ok(self)
    }
}

// ── Ssl ───────────────────────────────────────────────────────────────────────

/// Per-connection TLS object (`SSL*`).
///
/// Has exclusive ownership over its state; no `Clone`.  BIOs passed to
/// [`Ssl::set_bio_duplex`] or [`Ssl::set_bio`] are owned by the `Ssl` thereafter.
pub struct Ssl {
    ptr: *mut sys::SSL,
}

// SAFETY: `SSL*` is not thread-safe for concurrent access, but `Ssl` has
// exclusive ownership, so `Send` is safe for moving between threads.
unsafe impl Send for Ssl {}

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

impl Ssl {
    /// Set a single duplex BIO for both reading and writing.
    ///
    /// Transfers ownership of `bio` to the `SSL` object; do not use `bio`
    /// afterwards.  Suitable for `BIO_new_bio_pair` endpoints.
    ///
    /// When `rbio == wbio` (same pointer), OpenSSL only increments the
    /// reference count once, so the single reference in `bio` is correct.
    pub fn set_bio_duplex(&mut self, bio: Bio) {
        let ptr = bio.as_ptr();
        // Prevent our Drop from calling BIO_free — SSL now owns this BIO.
        std::mem::forget(bio);
        // Passing the same pointer for rbio and wbio: OpenSSL increments
        // the ref count only once when rbio == wbio.
        unsafe { sys::SSL_set_bio(self.ptr, ptr, ptr) };
    }

    /// Set separate read and write BIOs.
    ///
    /// Transfers ownership of both `rbio` and `wbio` to the `SSL` object.
    pub fn set_bio(&mut self, rbio: Bio, wbio: Bio) {
        let rbio_ptr = rbio.as_ptr();
        let wbio_ptr = wbio.as_ptr();
        // Prevent our Drop from calling BIO_free on each — SSL now owns them.
        std::mem::forget(rbio);
        std::mem::forget(wbio);
        unsafe { sys::SSL_set_bio(self.ptr, rbio_ptr, wbio_ptr) };
    }

    /// Set the SNI hostname extension sent during the TLS handshake.
    ///
    /// Call before [`Self::connect`] on client connections to enable SNI.
    /// `hostname` must be a NUL-terminated ASCII/UTF-8 hostname.
    ///
    /// `SSL_set_tlsext_host_name` is a C macro expanding to
    /// `SSL_ctrl(s, 55 /*SSL_CTRL_SET_TLSEXT_HOSTNAME*/, 0 /*TLSEXT_NAMETYPE_host_name*/, name)`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the control call fails.
    pub fn set_hostname(&mut self, hostname: &CStr) -> Result<(), ErrorStack> {
        let rc = unsafe {
            sys::SSL_ctrl(
                self.ptr,
                55, // SSL_CTRL_SET_TLSEXT_HOSTNAME
                0,  // TLSEXT_NAMETYPE_host_name
                // OpenSSL declares parg as *mut c_void but uses it as const here.
                hostname.as_ptr() as *mut std::os::raw::c_void,
            )
        };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Set this SSL object to operate in client (connect) mode.
    ///
    /// Required before calling [`Self::do_handshake`] if neither [`Self::connect`] nor
    /// [`Self::accept`] will be used.
    pub fn set_connect_state(&mut self) {
        unsafe { sys::SSL_set_connect_state(self.ptr) };
    }

    /// Set this SSL object to operate in server (accept) mode.
    pub fn set_accept_state(&mut self) {
        unsafe { sys::SSL_set_accept_state(self.ptr) };
    }

    /// Initiate a client-side TLS handshake (`SSL_connect`).
    ///
    /// Returns `Ok(())` on success, [`SslIoError::WantRead`] / [`SslIoError::WantWrite`]
    /// when the operation must be retried after more data is available.
    ///
    /// # Errors
    pub fn connect(&mut self) -> Result<(), SslIoError> {
        let rc = unsafe { sys::SSL_connect(self.ptr) };
        if rc == 1 {
            return Ok(());
        }
        Err(self.ssl_io_error(rc))
    }

    /// Accept an incoming TLS connection (`SSL_accept`).
    ///
    /// Returns `Ok(())` on success, [`SslIoError::WantRead`] / [`SslIoError::WantWrite`]
    /// on non-blocking retry.
    ///
    /// # Errors
    pub fn accept(&mut self) -> Result<(), SslIoError> {
        let rc = unsafe { sys::SSL_accept(self.ptr) };
        if rc == 1 {
            return Ok(());
        }
        Err(self.ssl_io_error(rc))
    }

    /// Drive the TLS handshake in either role (`SSL_do_handshake`).
    ///
    /// The role must have been set via [`Self::set_connect_state`] or [`Self::set_accept_state`]
    /// (or implicitly by [`Self::connect`] / [`Self::accept`]).
    ///
    /// # Errors
    pub fn do_handshake(&mut self) -> Result<(), SslIoError> {
        let rc = unsafe { sys::SSL_do_handshake(self.ptr) };
        if rc == 1 {
            return Ok(());
        }
        Err(self.ssl_io_error(rc))
    }

    /// Read decrypted application data (`SSL_read_ex`).
    ///
    /// Returns the number of bytes written into `buf` on success.
    ///
    /// # Errors
    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, SslIoError> {
        let mut readbytes: usize = 0;
        let rc = unsafe {
            sys::SSL_read_ex(
                self.ptr,
                buf.as_mut_ptr().cast(),
                buf.len(),
                std::ptr::addr_of_mut!(readbytes),
            )
        };
        if rc == 1 {
            return Ok(readbytes);
        }
        Err(self.ssl_io_error(rc))
    }

    /// Write application data (`SSL_write_ex`).
    ///
    /// Returns the number of bytes consumed from `buf` on success.
    ///
    /// # Errors
    pub fn write(&mut self, buf: &[u8]) -> Result<usize, SslIoError> {
        let mut written: usize = 0;
        let rc = unsafe {
            sys::SSL_write_ex(
                self.ptr,
                buf.as_ptr().cast(),
                buf.len(),
                std::ptr::addr_of_mut!(written),
            )
        };
        if rc == 1 {
            return Ok(written);
        }
        Err(self.ssl_io_error(rc))
    }

    /// Send a TLS close-notify alert (`SSL_shutdown`).
    ///
    /// Returns [`ShutdownResult::Sent`] after the first shutdown stage and
    /// [`ShutdownResult::Complete`] after a bidirectional shutdown.  Call
    /// twice on a non-blocking connection to complete the exchange.
    ///
    /// # Errors
    ///
    /// Returns `Err` on a fatal error during shutdown.
    pub fn shutdown(&mut self) -> Result<ShutdownResult, ErrorStack> {
        let rc = unsafe { sys::SSL_shutdown(self.ptr) };
        match rc {
            1 => Ok(ShutdownResult::Complete),
            0 => Ok(ShutdownResult::Sent),
            _ => Err(ErrorStack::drain()),
        }
    }

    /// Return the peer's full certificate chain (leaf + intermediates), or `None`.
    ///
    /// Each certificate in the returned `Vec` has its reference count independently
    /// incremented via `X509_up_ref`, so the certificates outlive `self`.
    ///
    /// Returns `None` when:
    /// - the handshake has not yet completed, or
    /// - the peer did not present a certificate (e.g. a server without client-auth
    ///   configured will see `None` for the client chain).
    ///
    /// An empty `Vec` is returned if the stack exists but contains no elements
    /// (unusual in practice).
    ///
    /// Note: this calls `SSL_get_peer_cert_chain`, which on the **server** side
    /// does not include the leaf certificate — only intermediates.  On the
    /// **client** side the full chain including the server leaf is returned.
    /// Use [`Self::peer_certificate`] to obtain the leaf cert in all cases.
    // i < n where n came from OPENSSL_sk_num (i32), so the i as i32 cast is safe.
    #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
    #[must_use]
    pub fn peer_cert_chain(&self) -> Option<Vec<X509>> {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant maintained by SslCtx::new_ssl)
        // - SSL_get_peer_cert_chain returns a borrowed stack valid for the lifetime
        //   of self; we immediately up_ref each cert so they are independently owned
        // - &self guarantees the SSL object is not mutated concurrently
        let stack = unsafe { sys::SSL_get_peer_cert_chain(self.ptr) };
        if stack.is_null() {
            return None;
        }
        // SAFETY: stack_st_X509 and OPENSSL_STACK (stack_st) are layout-compatible;
        // OpenSSL's typed stack macros cast between them internally.
        let n = unsafe { sys::OPENSSL_sk_num(stack.cast::<sys::OPENSSL_STACK>()) };
        let n = usize::try_from(n).unwrap_or(0);
        let mut certs = Vec::with_capacity(n);
        for i in 0..n {
            // SAFETY: i is in bounds (0..n); OPENSSL_sk_value returns a void*
            // which we cast to X509*. X509_up_ref is called before wrapping in X509
            // to ensure each element has its own reference.
            let raw =
                unsafe { sys::OPENSSL_sk_value(stack.cast::<sys::OPENSSL_STACK>(), i as i32) };
            if raw.is_null() {
                continue;
            }
            let cert_ptr = raw.cast::<sys::X509>();
            // SAFETY: cert_ptr is a valid X509* obtained from the stack; X509_up_ref
            // increments its reference count so the X509 wrapper owns an independent ref.
            unsafe { sys::X509_up_ref(cert_ptr) };
            certs.push(unsafe { X509::from_ptr(cert_ptr) });
        }
        Some(certs)
    }

    /// Return the peer's certificate, or `None` if unavailable.
    ///
    /// The returned certificate has its reference count incremented, so it
    /// outlives `self`.
    #[must_use]
    pub fn peer_certificate(&self) -> Option<X509> {
        let ptr = unsafe { sys::SSL_get0_peer_certificate(self.ptr) };
        if ptr.is_null() {
            return None;
        }
        // get0 → no ownership; increment ref count to produce an owned X509.
        unsafe { sys::X509_up_ref(ptr) };
        Some(unsafe { X509::from_ptr(ptr) })
    }

    /// Get an owned reference to the current session (`SSL_get1_session`).
    ///
    /// Returns `None` if no session is established.  The session can be passed
    /// to [`Self::set_session`] on a new `Ssl` for resumption.
    #[must_use]
    pub fn get1_session(&self) -> Option<SslSession> {
        let ptr = unsafe { sys::SSL_get1_session(self.ptr) };
        if ptr.is_null() {
            None
        } else {
            Some(SslSession { ptr })
        }
    }

    /// Borrow the current session without incrementing the reference count
    /// (`SSL_get_session`).
    ///
    /// Returns `None` if no session is associated with this connection (e.g.
    /// the handshake has not yet completed or session caching is disabled).
    ///
    /// The returned [`BorrowedSslSession`] is valid for the lifetime of `self`
    /// and must **not** be retained beyond it.  Use [`Self::get1_session`]
    /// if you need a session that outlives the connection.
    ///
    /// # Lifetimes
    ///
    /// The borrow is tied to `'_` (the lifetime of `self`): the `SSL_SESSION*`
    /// is owned by the `SSL` object and is invalidated when the `Ssl` is dropped
    /// or a new session is negotiated.
    #[must_use]
    pub fn session(&self) -> Option<BorrowedSslSession<'_>> {
        // SAFETY: `self.ptr` is a valid, non-null `SSL*` for the lifetime of
        // `self`. `SSL_get_session` returns either NULL or a pointer whose
        // lifetime is bounded by the `SSL` object — no refcount bump occurs, so
        // we wrap it in `ManuallyDrop` to prevent `SSL_SESSION_free` from being
        // called when the `BorrowedSslSession` is dropped.
        let ptr = unsafe { sys::SSL_get_session(self.ptr) };
        if ptr.is_null() {
            None
        } else {
            Some(BorrowedSslSession {
                inner: ManuallyDrop::new(SslSession { ptr }),
                _marker: std::marker::PhantomData,
            })
        }
    }

    /// Set a previously obtained session for resumption (`SSL_set_session`).
    ///
    /// Call before the handshake.
    ///
    /// # Errors
    pub fn set_session(&mut self, session: &SslSession) -> Result<(), ErrorStack> {
        let rc = unsafe { sys::SSL_set_session(self.ptr, session.ptr) };
        if rc != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Override the expected server hostname for this connection's certificate
    /// verification.
    ///
    /// Use this when one [`SslCtx`] is shared across connections to different
    /// servers and per-connection hostname checking is needed.  Wraps
    /// `SSL_set1_host`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if `hostname` contains a NUL byte or if `SSL_set1_host`
    /// fails.
    pub fn set_verify_hostname(&mut self, hostname: &str) -> Result<(), ErrorStack> {
        let cstr = std::ffi::CString::new(hostname).map_err(|_| ErrorStack::drain())?;
        // SAFETY: self.ptr is non-null (constructor invariant); cstr is valid for
        // the duration of this call; &mut self guarantees exclusive access.
        let ret = unsafe { sys::SSL_set1_host(self.ptr, cstr.as_ptr()) };
        if ret != 1 {
            return Err(ErrorStack::drain());
        }
        Ok(())
    }

    /// Translate a non-positive SSL I/O return code into an [`SslIoError`].
    fn ssl_io_error(&self, ret: i32) -> SslIoError {
        let err = unsafe { sys::SSL_get_error(self.ptr, ret) };
        match err {
            2 => SslIoError::WantRead,
            3 => SslIoError::WantWrite,
            5 => SslIoError::Syscall(ErrorStack::drain()),
            6 => SslIoError::ZeroReturn,
            _ => {
                let stack = ErrorStack::drain();
                if stack.errors().next().is_none() {
                    SslIoError::Other(err)
                } else {
                    SslIoError::Ssl(stack)
                }
            }
        }
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::pkey::{KeygenCtx, Pkey, Private, Public};
    use crate::x509::{X509Builder, X509NameOwned};

    // ── Helpers ───────────────────────────────────────────────────────────────

    /// Generate a fresh Ed25519 key pair.
    fn make_ed25519_key() -> (Pkey<Private>, Pkey<Public>) {
        let mut kgen = KeygenCtx::new(c"ED25519").unwrap();
        let priv_key = kgen.generate().unwrap();
        let pub_key = Pkey::<Public>::from(priv_key.clone());
        (priv_key, pub_key)
    }

    /// Build a self-signed Ed25519 certificate valid for 1 day.
    fn make_self_signed_cert(priv_key: &Pkey<Private>, pub_key: &Pkey<Public>) -> X509 {
        let mut name = X509NameOwned::new().unwrap();
        name.add_entry_by_txt(c"CN", b"test").unwrap();

        X509Builder::new()
            .unwrap()
            .set_version(2)
            .unwrap()
            .set_serial_number(1)
            .unwrap()
            .set_not_before_offset(0)
            .unwrap()
            .set_not_after_offset(86400)
            .unwrap()
            .set_subject_name(&name)
            .unwrap()
            .set_issuer_name(&name)
            .unwrap()
            .set_public_key(pub_key)
            .unwrap()
            .sign(priv_key, None)
            .unwrap()
            .build()
    }

    /// Drive a pair of SSL objects to a completed handshake using an in-memory
    /// BIO pair.  Returns `(client, server)` after the handshake.
    ///
    /// `BIO_new_bio_pair` creates two linked BIOs:
    ///   - data written to bio1 → readable from bio2  (client→server)
    ///   - data written to bio2 → readable from bio1  (server→client)
    fn do_handshake_pair(mut client: Ssl, mut server: Ssl) -> Result<(Ssl, Ssl), SslIoError> {
        let mut client_bio: *mut sys::BIO = std::ptr::null_mut();
        let mut server_bio: *mut sys::BIO = std::ptr::null_mut();
        let rc = unsafe {
            sys::BIO_new_bio_pair(
                std::ptr::addr_of_mut!(client_bio),
                0,
                std::ptr::addr_of_mut!(server_bio),
                0,
            )
        };
        assert_eq!(rc, 1, "BIO_new_bio_pair failed");

        // Transfer ownership to the SSL objects.  Since rbio == wbio for each,
        // OpenSSL only takes one ref, matching the single ref from BIO_new_bio_pair.
        let client_bio_obj = unsafe { Bio::from_ptr_owned(client_bio) };
        let server_bio_obj = unsafe { Bio::from_ptr_owned(server_bio) };
        client.set_bio_duplex(client_bio_obj);
        server.set_bio_duplex(server_bio_obj);

        // Alternate between client and server until both complete (up to 20 steps).
        let mut client_done = false;
        let mut server_done = false;

        for _ in 0..20 {
            if !client_done {
                match client.connect() {
                    Ok(()) => client_done = true,
                    Err(SslIoError::WantRead | SslIoError::WantWrite) => {}
                    Err(e) => return Err(e),
                }
            }
            if !server_done {
                match server.accept() {
                    Ok(()) => server_done = true,
                    Err(SslIoError::WantRead | SslIoError::WantWrite) => {}
                    Err(e) => return Err(e),
                }
            }
            if client_done && server_done {
                return Ok((client, server));
            }
        }
        Err(SslIoError::Other(-1))
    }

    // ── SslCtx construction ───────────────────────────────────────────────────

    #[test]
    fn ctx_new_variants() {
        SslCtx::new().unwrap();
        SslCtx::new_client().unwrap();
        SslCtx::new_server().unwrap();
    }

    #[test]
    fn ctx_clone() {
        let ctx = SslCtx::new().unwrap();
        let _clone = ctx.clone();
    }

    #[test]
    fn ctx_proto_version() {
        let ctx = SslCtx::new().unwrap();
        ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        ctx.set_max_proto_version(TlsVersion::Tls13).unwrap();
    }

    #[test]
    fn ctx_verify_mode() {
        let ctx = SslCtx::new().unwrap();
        ctx.set_verify(SslVerifyMode::NONE);
        ctx.set_verify(SslVerifyMode::PEER);
        ctx.set_verify(SslVerifyMode::PEER.or(SslVerifyMode::FAIL_IF_NO_PEER_CERT));
    }

    #[test]
    fn ctx_cipher_list() {
        let ctx = SslCtx::new().unwrap();
        ctx.set_cipher_list(c"HIGH:!aNULL").unwrap();
    }

    // ── Certificate / key loading ─────────────────────────────────────────────

    #[test]
    fn ctx_load_cert_and_key() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let ctx = SslCtx::new_server().unwrap();
        ctx.use_certificate(&cert).unwrap();
        ctx.use_private_key(&priv_key).unwrap();
        ctx.check_private_key().unwrap();
    }

    // ── In-memory TLS handshake ───────────────────────────────────────────────

    #[test]
    fn tls13_handshake_ed25519() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        // Server context: present our self-signed cert.
        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.set_min_proto_version(TlsVersion::Tls13).unwrap();
        server_ctx.set_max_proto_version(TlsVersion::Tls13).unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.check_private_key().unwrap();
        server_ctx.disable_session_cache();

        // Client context: do not verify the server cert (self-signed test cert).
        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_min_proto_version(TlsVersion::Tls13).unwrap();
        client_ctx.set_max_proto_version(TlsVersion::Tls13).unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let client_ssl = client_ctx.new_ssl().unwrap();
        let server_ssl = server_ctx.new_ssl().unwrap();

        let (mut client, mut server) =
            do_handshake_pair(client_ssl, server_ssl).expect("TLS 1.3 handshake failed");

        // Exchange a small message.
        let msg = b"hello TLS 1.3";
        let n = client.write(msg).unwrap();
        assert_eq!(n, msg.len());

        let mut buf = [0u8; 64];
        let n = server.read(&mut buf).unwrap();
        assert_eq!(&buf[..n], msg);

        // Server replies.
        let reply = b"world";
        server.write(reply).unwrap();
        let n = client.read(&mut buf).unwrap();
        assert_eq!(&buf[..n], reply);
    }

    #[test]
    fn tls12_handshake() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.check_private_key().unwrap();
        server_ctx.disable_session_cache();

        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let client_ssl = client_ctx.new_ssl().unwrap();
        let server_ssl = server_ctx.new_ssl().unwrap();

        let (mut client, mut server) =
            do_handshake_pair(client_ssl, server_ssl).expect("TLS 1.2 handshake failed");

        // Verify data exchange works after handshake.
        client.write(b"tls12").unwrap();
        let mut buf = [0u8; 16];
        let n = server.read(&mut buf).unwrap();
        assert_eq!(&buf[..n], b"tls12");
    }

    #[test]
    fn peer_certificate_after_handshake() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        // Re-encode cert to DER for comparison.
        let cert_der = cert.to_der().unwrap();

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.disable_session_cache();

        // Client requests peer cert verification (peer cert = server cert).
        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let (client, _server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .unwrap();

        // The client should have the server's certificate.
        let peer_cert = client.peer_certificate().expect("no peer certificate");
        let peer_der = peer_cert.to_der().unwrap();
        assert_eq!(peer_der, cert_der, "peer cert DER mismatch");
    }

    #[test]
    fn shutdown_sequence() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.disable_session_cache();

        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let (mut client, mut server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .unwrap();

        // Client sends close_notify → Sent.
        let r = client.shutdown().unwrap();
        assert_eq!(r, ShutdownResult::Sent);

        // Server receives close_notify; its first shutdown call returns Sent.
        let r = server.shutdown().unwrap();
        assert_eq!(r, ShutdownResult::Sent);

        // Client receives server's close_notify → Complete.
        let r = client.shutdown().unwrap();
        assert_eq!(r, ShutdownResult::Complete);
    }

    #[test]
    fn verify_mode_bits() {
        let both = SslVerifyMode::PEER.or(SslVerifyMode::FAIL_IF_NO_PEER_CERT);
        assert_eq!(both.0, 0x03);
    }

    // ── peer_cert_chain ───────────────────────────────────────────────────────

    #[test]
    fn peer_cert_chain_none_before_handshake() {
        // A fresh SSL object with no completed handshake must return None.
        let ctx = SslCtx::new_client().unwrap();
        let ssl = ctx.new_ssl().unwrap();
        assert!(
            ssl.peer_cert_chain().is_none(),
            "expected no peer cert chain before handshake"
        );
    }

    #[test]
    fn peer_cert_chain_after_handshake() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        // Re-encode the server cert to DER for comparison.
        let cert_der = cert.to_der().unwrap();

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.disable_session_cache();

        // Client: do not verify (self-signed test cert).
        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let (client, _server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .expect("handshake failed");

        // SSL_get_peer_cert_chain on the client side returns the server's chain.
        // For a self-signed cert there is exactly one entry: the leaf.
        let chain = client
            .peer_cert_chain()
            .expect("expected Some peer cert chain after handshake");
        assert!(
            !chain.is_empty(),
            "peer cert chain must contain at least one certificate"
        );
        // The first entry in the chain is the server's leaf certificate.
        let leaf_der = chain[0].to_der().unwrap();
        assert_eq!(
            leaf_der, cert_der,
            "first cert in chain must match server leaf cert"
        );
    }

    // ── session() borrowed accessor ───────────────────────────────────────────

    #[test]
    fn session_none_before_handshake() {
        // A fresh SSL object with no handshake should have no session.
        let ctx = SslCtx::new_client().unwrap();
        let ssl = ctx.new_ssl().unwrap();
        assert!(
            ssl.session().is_none(),
            "expected no session before handshake"
        );
    }

    #[test]
    fn session_some_after_handshake_tls12() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.check_private_key().unwrap();
        // Session caching must be enabled for a session to be stored.
        // (Default for a new SslCtx is enabled, so we do NOT call disable_session_cache.)

        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);

        let (client, _server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .expect("TLS 1.2 handshake failed");

        // TLS 1.2 always produces a session object; get0 variant must return Some.
        let borrowed = client
            .session()
            .expect("expected Some session after TLS 1.2 handshake");

        // The borrowed session must be backed by the same pointer as get1_session.
        let owned = client
            .get1_session()
            .expect("get1_session must also return Some");
        assert_eq!(
            borrowed.ptr, owned.ptr,
            "borrowed and owned sessions should point to the same SSL_SESSION"
        );
    }

    #[test]
    fn borrowed_session_does_not_free() {
        // Verify that dropping BorrowedSslSession does not cause a use-after-free:
        // after drop, get1_session must still succeed (refcount not decreased).
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let server_ctx = SslCtx::new_server().unwrap();
        server_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        server_ctx.use_certificate(&cert).unwrap();
        server_ctx.use_private_key(&priv_key).unwrap();
        server_ctx.check_private_key().unwrap();

        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_min_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_max_proto_version(TlsVersion::Tls12).unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);

        let (client, _server) =
            do_handshake_pair(client_ctx.new_ssl().unwrap(), server_ctx.new_ssl().unwrap())
                .expect("handshake failed");

        {
            let _borrowed = client.session().expect("session must be Some");
            // _borrowed is dropped here; must not call SSL_SESSION_free.
        }

        // If the borrowed session incorrectly freed the pointer, this would crash.
        let _owned = client
            .get1_session()
            .expect("session still accessible after borrowed drop");
    }

    // ── SslCtxBuilder tests ───────────────────────────────────────────────────

    #[test]
    fn ssl_ctx_builder_server_basic() {
        let ctx = SslCtxBuilder::<Server>::new().unwrap().build().unwrap();
        // Verify that the resulting SslCtx can create an SSL object.
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_client_basic() {
        let ctx = SslCtxBuilder::<Client>::new().unwrap().build().unwrap();
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_client_cipher_list() {
        let ctx = SslCtxBuilder::<Client>::new()
            .unwrap()
            .cipher_list(c"HIGH:!aNULL")
            .unwrap()
            .build()
            .unwrap();
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_client_alpn_protos_list() {
        let ctx = SslCtxBuilder::<Client>::new()
            .unwrap()
            .alpn_protos_list(&["h2", "http/1.1"])
            .unwrap()
            .build()
            .unwrap();
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_client_default_ca_paths() {
        let ctx = SslCtxBuilder::<Client>::new()
            .unwrap()
            .default_ca_paths()
            .unwrap()
            .verify_peer()
            .build()
            .unwrap();
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_client_verify_hostname() {
        let ctx = SslCtxBuilder::<Client>::new()
            .unwrap()
            .verify_hostname("example.com")
            .unwrap()
            .build()
            .unwrap();
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_server_with_cert_key() {
        let (priv_key, pub_key) = make_ed25519_key();
        let cert = make_self_signed_cert(&priv_key, &pub_key);

        let ctx = SslCtxBuilder::<Server>::new()
            .unwrap()
            .certificate(&cert)
            .unwrap()
            .private_key(&priv_key)
            .unwrap()
            .check_private_key()
            .unwrap()
            .build()
            .unwrap();

        // Verify the resulting context can participate in a handshake.
        let client_ctx = SslCtx::new_client().unwrap();
        client_ctx.set_verify(SslVerifyMode::NONE);
        client_ctx.disable_session_cache();

        let server_ssl = ctx.new_ssl().unwrap();
        let client_ssl = client_ctx.new_ssl().unwrap();
        do_handshake_pair(client_ssl, server_ssl).expect("builder server handshake failed");
    }

    #[test]
    fn ssl_set_verify_hostname_per_connection() {
        // Verify that Ssl::set_verify_hostname does not error on a valid hostname.
        let ctx = SslCtx::new_client().unwrap();
        let mut ssl = ctx.new_ssl().unwrap();
        ssl.set_verify_hostname("example.com").unwrap();
    }

    #[test]
    fn ssl_ctx_builder_server_proto_versions() {
        let ctx = SslCtxBuilder::<Server>::new()
            .unwrap()
            .min_proto_version(TlsVersion::Tls12)
            .unwrap()
            .max_proto_version(TlsVersion::Tls13)
            .unwrap()
            .build()
            .unwrap();
        ctx.new_ssl().unwrap();
    }

    #[test]
    fn ssl_ctx_builder_verify_hostname_flags_no_partial_wildcards() {
        // Verify that setting HostnameFlags::NO_PARTIAL_WILDCARDS does not error.
        let ctx = SslCtxBuilder::<Client>::new()
            .unwrap()
            .verify_hostname_flags(HostnameFlags::NO_PARTIAL_WILDCARDS)
            .unwrap()
            .build()
            .unwrap();
        ctx.new_ssl().unwrap();
    }
}
