//! Random number generation — `Rand` (simple), `RandAlg`, and `RandCtx`.
//!
//! Phase 3.4 delivers `RandAlg`; Phase 4.4 extends this module with
//! `Rand` and `RandCtx`.

use crate::error::ErrorStack;
use native_ossl_sys as sys;
use std::ffi::CStr;
use std::sync::Arc;

// ── RandAlg — algorithm descriptor ───────────────────────────────────────────

/// An OpenSSL RAND algorithm descriptor (`EVP_RAND*`).
///
/// Fetched once and reused to create `RandCtx` instances.
pub struct RandAlg {
    ptr: *mut sys::EVP_RAND,
    /// Keeps the library context alive for the lifetime of this descriptor.
    /// `EVP_RAND*` is bound to the context it was fetched from; dropping the
    /// context before the algorithm descriptor would leave the pointer dangling.
    _lib_ctx: Option<Arc<crate::lib_ctx::LibCtx>>,
}

impl RandAlg {
    /// Fetch a RAND algorithm from the global default library context.
    ///
    /// Common algorithm names: `c"CTR-DRBG"`, `c"HASH-DRBG"`, `c"HMAC-DRBG"`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the algorithm is not available.
    pub fn fetch(name: &CStr, props: Option<&CStr>) -> Result<Self, ErrorStack> {
        let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
        let ptr = unsafe { sys::EVP_RAND_fetch(std::ptr::null_mut(), name.as_ptr(), props_ptr) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(RandAlg {
            ptr,
            _lib_ctx: None,
        })
    }

    /// Fetch a RAND algorithm within an explicit library context.
    ///
    /// Use this when the DRBG must be bound to a specific provider set
    /// (e.g. a FIPS-isolated `LibCtx`).
    ///
    /// The `Arc<LibCtx>` is retained for the lifetime of the `RandAlg` to
    /// ensure the context outlives the algorithm descriptor.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the algorithm is not available in `ctx`.
    pub fn fetch_in(
        ctx: &Arc<crate::lib_ctx::LibCtx>,
        name: &CStr,
        props: Option<&CStr>,
    ) -> Result<Self, ErrorStack> {
        let props_ptr = props.map_or(std::ptr::null(), CStr::as_ptr);
        let ptr = unsafe { sys::EVP_RAND_fetch(ctx.as_ptr(), name.as_ptr(), props_ptr) };
        if ptr.is_null() {
            return Err(ErrorStack::drain());
        }
        Ok(RandAlg {
            ptr,
            _lib_ctx: Some(Arc::clone(ctx)),
        })
    }

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

    /// Security strength of this algorithm descriptor in bits.
    ///
    /// Calls `EVP_RAND_get_params` with the `"strength"` key to read the
    /// declared security strength of the algorithm (e.g. 256 for CTR-DRBG
    /// with AES-256, 128 for HMAC-DRBG with SHA-256).
    ///
    /// # Errors
    ///
    /// Returns `Err` if the OpenSSL parameter query fails.
    pub fn params_strength(&self) -> Result<u32, ErrorStack> {
        // Build a one-element query array with an initial value of 0.
        // OSSL_PARAM_BLD_push_uint records the key name and the u32 type;
        // EVP_RAND_get_params fills in the actual value in-place.
        let mut params = crate::params::ParamBuilder::new()?
            .push_uint(c"strength", 0)?
            .build()?;
        // SAFETY:
        // - self.ptr is non-null: guaranteed by `fetch` / `fetch_in` which
        //   return Err on null before constructing RandAlg.
        // - Lifetime: params lives for the duration of this call; the mutable
        //   pointer is valid until params is dropped at the end of this scope.
        // - Exclusivity: no other reference to the Params array exists; we
        //   own it exclusively for the duration of this call.
        crate::ossl_call!(sys::EVP_RAND_get_params(self.ptr, params.as_mut_ptr()))?;
        params.get_uint(c"strength")
    }
}

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

// SAFETY: `EVP_RAND*` is reference-counted and immutable after fetch.
unsafe impl Send for RandAlg {}
unsafe impl Sync for RandAlg {}

// ── Rand — simple fill helpers (Phase 4.4) ────────────────────────────────────

/// Zero-size type with static methods wrapping `RAND_bytes` / `RAND_priv_bytes`.
pub struct Rand;

impl Rand {
    /// Fill `buf` with cryptographically random bytes.
    ///
    /// Zero-copy: writes directly into the caller's buffer.
    ///
    /// # Panics
    ///
    /// Panics if `buf.len() > i32::MAX`.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the PRNG fails (e.g. not seeded).
    pub fn fill(buf: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
        let n = i32::try_from(buf.len()).expect("buffer too large for RAND_bytes");
        let rc = unsafe { sys::RAND_bytes(buf.as_mut_ptr(), n) };
        if rc != 1 {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(())
    }

    /// Fill `buf` with private (non-predictable) random bytes.
    ///
    /// # Panics
    ///
    /// Panics if `buf.len() > i32::MAX`.
    ///
    /// # Errors
    pub fn fill_private(buf: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
        let n = i32::try_from(buf.len()).expect("buffer too large for RAND_priv_bytes");
        let rc = unsafe { sys::RAND_priv_bytes(buf.as_mut_ptr(), n) };
        if rc != 1 {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(())
    }

    /// Allocate and fill a `Vec<u8>` of `n` random bytes.
    ///
    /// Prefer `fill` when the destination buffer is already allocated.
    ///
    /// # Errors
    pub fn bytes(n: usize) -> Result<Vec<u8>, crate::error::ErrorStack> {
        let mut buf = vec![0u8; n];
        Self::fill(&mut buf)?;
        Ok(buf)
    }

    /// Allocate and fill a `Vec<u8>` of `n` private random bytes.
    ///
    /// Equivalent to `bytes` but uses `RAND_priv_bytes` — suitable for key
    /// material and other values that must not be disclosed.
    ///
    /// # Errors
    pub fn bytes_private(n: usize) -> Result<Vec<u8>, crate::error::ErrorStack> {
        let mut buf = vec![0u8; n];
        Self::fill_private(&mut buf)?;
        Ok(buf)
    }
}

// ── GenerateRequest — named struct for EVP_RAND_generate parameters ───────────

/// Parameters for `RandCtx::generate`.
///
/// Use struct update syntax for partial overrides:
/// ```ignore
/// GenerateRequest { prediction_resistance: true, ..Default::default() }
/// ```
pub struct GenerateRequest<'a> {
    /// Requested security strength in bits (e.g. 256).
    pub strength: u32,
    /// If `true`, OpenSSL reseeds before generating.
    pub prediction_resistance: bool,
    /// Optional additional input bytes fed into the DRBG.
    pub additional_input: Option<&'a [u8]>,
}

impl Default for GenerateRequest<'_> {
    fn default() -> Self {
        GenerateRequest {
            strength: 256,
            prediction_resistance: false,
            additional_input: None,
        }
    }
}

// ── RandState ─────────────────────────────────────────────────────────────────

/// Lifecycle state of a `RandCtx` DRBG instance.
///
/// Returned by [`RandCtx::state`]. Maps the `EVP_RAND_STATE_*` constants
/// from `<openssl/evp.h>`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RandState {
    /// Context created but not yet instantiated (seeded).
    Uninitialised,
    /// Context is seeded and ready to generate.
    Ready,
    /// Context entered an unrecoverable error state.
    Error,
    /// Unrecognised value returned by OpenSSL (forward-compat guard).
    Unknown(i32),
}

// ── RandCtx — EVP_RAND_CTX wrapper ───────────────────────────────────────────

/// A seeded DRBG context (`EVP_RAND_CTX*`).
///
/// Has `up_ref` so `Clone` is implemented; wrapping in `Arc<RandCtx>` is safe.
pub struct RandCtx {
    ptr: *mut sys::EVP_RAND_CTX,
}

impl RandCtx {
    /// Create a new uninstantiated DRBG context from an algorithm descriptor.
    ///
    /// Provide `parent = None` to seed from the global PRNG.
    ///
    /// # Errors
    pub fn new(alg: &RandAlg, parent: Option<&RandCtx>) -> Result<Self, crate::error::ErrorStack> {
        let parent_ptr = parent.map_or(std::ptr::null_mut(), |p| p.ptr);
        let ptr = unsafe { sys::EVP_RAND_CTX_new(alg.as_ptr(), parent_ptr) };
        if ptr.is_null() {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(RandCtx { ptr })
    }

    /// Instantiate (seed) the DRBG at the requested security strength.
    ///
    /// Must be called before `generate` if the context was not auto-seeded.
    /// When `parent = None` was used in `new`, pass `strength ≤ 128` to stay
    /// within the system seed-source's entropy ceiling.
    ///
    /// # Errors
    pub fn instantiate(
        &mut self,
        strength: u32,
        prediction_resistance: bool,
        params: Option<&crate::params::Params<'_>>,
    ) -> Result<(), crate::error::ErrorStack> {
        let params_ptr = params.map_or(crate::params::null_params(), crate::params::Params::as_ptr);
        let rc = unsafe {
            sys::EVP_RAND_instantiate(
                self.ptr,
                strength,
                i32::from(prediction_resistance),
                std::ptr::null(),
                0,
                params_ptr,
            )
        };
        if rc != 1 {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(())
    }

    /// Generate random bytes into `out`.
    ///
    /// Zero-copy: writes directly into the caller's slice.
    ///
    /// # Errors
    pub fn generate(
        &mut self,
        out: &mut [u8],
        req: &GenerateRequest<'_>,
    ) -> Result<(), crate::error::ErrorStack> {
        let (ai_ptr, ai_len) = req
            .additional_input
            .map_or((std::ptr::null(), 0), |s| (s.as_ptr(), s.len()));

        let rc = unsafe {
            sys::EVP_RAND_generate(
                self.ptr,
                out.as_mut_ptr(),
                out.len(),
                req.strength,
                i32::from(req.prediction_resistance),
                ai_ptr,
                ai_len,
            )
        };
        if rc != 1 {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(())
    }

    /// Generate random bytes with default parameters (strength=256, no prediction
    /// resistance, no additional input).
    ///
    /// Equivalent to calling `generate` with [`GenerateRequest::default`].
    /// Call [`Self::instantiate`] before the first `fill`.
    ///
    /// # Errors
    pub fn fill(&mut self, out: &mut [u8]) -> Result<(), crate::error::ErrorStack> {
        self.generate(out, &GenerateRequest::default())
    }

    /// Security strength of this DRBG instance in bits.
    #[must_use]
    pub fn strength(&self) -> u32 {
        unsafe { sys::EVP_RAND_get_strength(self.ptr) }
    }

    /// Current lifecycle state of this DRBG context.
    ///
    /// Returns [`RandState::Ready`] after a successful [`Self::instantiate`].
    #[must_use]
    pub fn state(&self) -> RandState {
        match unsafe { sys::EVP_RAND_get_state(self.ptr) } {
            0 => RandState::Uninitialised,
            1 => RandState::Ready,
            2 => RandState::Error,
            n => RandState::Unknown(n),
        }
    }

    /// Set DRBG parameters (e.g. reseed interval, additional input length).
    ///
    /// # Errors
    ///
    /// Returns `Err` if `EVP_RAND_CTX_set_params` fails.
    pub fn set_params(&mut self, params: &crate::params::Params<'_>) -> Result<(), ErrorStack> {
        // SAFETY:
        // - self.ptr is non-null (constructor invariant)
        // - params.as_ptr() is valid for the duration of this call
        // - &mut self ensures exclusive access
        crate::ossl_call!(sys::EVP_RAND_CTX_set_params(self.ptr, params.as_ptr()))
    }

    /// Return a reference to the process-wide **public** DRBG.
    ///
    /// The returned [`GlobalRandCtx`] does **not** free the pointer when dropped —
    /// the global DRBG is owned by the OpenSSL runtime.
    ///
    /// Pass `Some(global.as_rand_ctx())` as the `parent` argument to
    /// [`RandCtx::new`] to chain a child DRBG off the global instance.
    ///
    /// Requires OpenSSL 3.2+.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the global DRBG is not yet initialised.
    #[cfg(ossl320)]
    pub fn public() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
        let ptr = unsafe { sys::RAND_get0_public(std::ptr::null_mut()) };
        if ptr.is_null() {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
    }

    /// Return a reference to the process-wide **private** DRBG.
    ///
    /// Same semantics as [`RandCtx::public`].
    ///
    /// Requires OpenSSL 3.2+.
    ///
    /// # Errors
    #[cfg(ossl320)]
    pub fn private_global() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
        let ptr = unsafe { sys::RAND_get0_private(std::ptr::null_mut()) };
        if ptr.is_null() {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
    }

    /// Return a reference to the process-wide **primary** DRBG.
    ///
    /// The primary DRBG is the root of the global DRBG hierarchy — the public
    /// and private DRBGs are seeded from it. It is useful for diagnostics and
    /// for configuring the root of a custom DRBG chain.
    ///
    /// Same ownership semantics as [`RandCtx::public`]: the returned
    /// [`GlobalRandCtx`] does **not** free the pointer on drop.
    ///
    /// Requires OpenSSL 3.2+.
    ///
    /// # Errors
    ///
    /// Returns `Err` if the primary DRBG is not yet initialised.
    #[cfg(ossl320)]
    pub fn primary() -> Result<GlobalRandCtx, crate::error::ErrorStack> {
        // SAFETY:
        // - null_mut() selects the default library context (acceptable per OpenSSL docs)
        // - RAND_get0_primary returns a borrowed pointer to the process-wide DRBG;
        //   ManuallyDrop ensures it is never freed through this handle
        // - no mutable aliasing: all callers hold a shared borrow via GlobalRandCtx
        let ptr = unsafe { sys::RAND_get0_primary(std::ptr::null_mut()) };
        if ptr.is_null() {
            return Err(crate::error::ErrorStack::drain());
        }
        Ok(GlobalRandCtx(std::mem::ManuallyDrop::new(RandCtx { ptr })))
    }
}

// ── GlobalRandCtx (OpenSSL 3.2+) ─────────────────────────────────────────────

/// A borrowed handle to one of the process-wide global DRBGs.
///
/// Obtained from [`RandCtx::public`] or [`RandCtx::private_global`].
///
/// **Does not implement `Clone` or `Drop`** — the global DRBG is owned by the
/// OpenSSL runtime and must not be freed through this handle.  Implements
/// `Deref<Target = RandCtx>` so all read-only `RandCtx` operations are available,
/// and `as_rand_ctx()` provides a `&RandCtx` to pass as a parent to [`RandCtx::new`].
#[cfg(ossl320)]
pub struct GlobalRandCtx(std::mem::ManuallyDrop<RandCtx>);

// SAFETY: the underlying EVP_RAND_CTX* is the global DRBG; it is safe to move
// this handle to another thread.
#[cfg(ossl320)]
unsafe impl Send for GlobalRandCtx {}

#[cfg(ossl320)]
impl std::ops::Deref for GlobalRandCtx {
    type Target = RandCtx;
    fn deref(&self) -> &RandCtx {
        &self.0
    }
}

#[cfg(ossl320)]
impl GlobalRandCtx {
    /// Borrow as `&RandCtx` for use as the `parent` argument in [`RandCtx::new`].
    #[must_use]
    pub fn as_rand_ctx(&self) -> &RandCtx {
        &self.0
    }
}

// EVP_RAND_CTX_up_ref was added in OpenSSL 3.1.0.
#[cfg(ossl310)]
impl Clone for RandCtx {
    fn clone(&self) -> Self {
        unsafe { sys::EVP_RAND_CTX_up_ref(self.ptr) };
        RandCtx { ptr: self.ptr }
    }
}

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

unsafe impl Send for RandCtx {}
unsafe impl Sync for RandCtx {}

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

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

    #[test]
    fn fetch_ctr_drbg_succeeds() {
        let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
        drop(alg);
    }

    #[test]
    fn fetch_nonexistent_fails() {
        assert!(RandAlg::fetch(c"NONEXISTENT_RAND_XYZ", None).is_err());
    }

    #[test]
    fn rand_fill_nonzero() {
        let mut buf = [0u8; 32];
        Rand::fill(&mut buf).unwrap();
        // Probability of all-zero is 2^(-256) — treat as impossible.
        assert_ne!(buf, [0u8; 32]);
    }

    #[test]
    fn rand_bytes_len() {
        let v = Rand::bytes(64).unwrap();
        assert_eq!(v.len(), 64);
    }

    #[cfg(ossl320)]
    #[test]
    fn rand_primary_is_accessible() {
        let primary = RandCtx::primary().expect("primary DRBG must be initialised");
        // The primary DRBG should be in the Ready state after OpenSSL initialisation.
        assert_eq!(primary.state(), RandState::Ready);
    }

    #[cfg(ossl320)]
    #[test]
    fn rand_primary_strength_nonzero() {
        let primary = RandCtx::primary().unwrap();
        assert!(
            primary.strength() > 0,
            "primary DRBG must report non-zero strength"
        );
    }

    #[test]
    fn ctr_drbg_params_strength_call_succeeds() {
        // EVP_RAND_get_params on an algorithm descriptor does not populate
        // "strength" on OpenSSL 3.5+ (strength depends on instantiation config).
        // Verify the call does not error; the returned value may legitimately be 0.
        let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
        alg.params_strength().unwrap();
    }

    #[test]
    fn hmac_drbg_params_strength_call_succeeds() {
        // Same limitation as CTR-DRBG: algorithm-level params_strength may be 0.
        let alg = RandAlg::fetch(c"HMAC-DRBG", None).unwrap();
        alg.params_strength().unwrap();
    }

    #[test]
    fn rand_ctx_two_outputs_differ() {
        let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
        let mut ctx = RandCtx::new(&alg, None).unwrap();
        // The default CTR-DRBG uses AES-256-CTR, which requires 256-bit entropy
        // from SEED-SRC.  Many systems cap at 128 bits without a parent DRBG.
        // Explicitly configure AES-128-CTR (128-bit strength) so instantiation
        // succeeds against the system SEED-SRC.
        let params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_utf8_string(c"cipher", c"AES-128-CTR")
            .unwrap()
            .build()
            .unwrap();
        ctx.instantiate(128, false, Some(&params)).unwrap();

        let req = GenerateRequest {
            strength: 128,
            ..Default::default()
        };
        let mut a = [0u8; 32];
        let mut b = [0u8; 32];
        ctx.generate(&mut a, &req).unwrap();
        ctx.generate(&mut b, &req).unwrap();
        // Two consecutive generates should produce different output.
        assert_ne!(a, b);
    }

    #[test]
    fn rand_ctx_set_params_strength() {
        let alg = RandAlg::fetch(c"CTR-DRBG", None).unwrap();
        let mut ctx = RandCtx::new(&alg, None).unwrap();
        // Configure AES-128-CTR so instantiation succeeds against SEED-SRC.
        let inst_params = crate::params::ParamBuilder::new()
            .unwrap()
            .push_utf8_string(c"cipher", c"AES-128-CTR")
            .unwrap()
            .build()
            .unwrap();
        ctx.instantiate(128, false, Some(&inst_params)).unwrap();

        // Build a params array with OSSL_DRBG_PARAM_RESEED_REQUESTS.
        // The key is "reseed_requests" — after instantiation this should be
        // accepted by EVP_RAND_set_params without error.
        let set_p = crate::params::ParamBuilder::new()
            .unwrap()
            .push_uint(c"reseed_requests", 512)
            .unwrap()
            .build()
            .unwrap();
        ctx.set_params(&set_p).expect("set_params should succeed");
    }
}
