//! FIPS provider helpers.
//!
//! [`is_running`] is always available — it uses the public
//! `OSSL_PROVIDER_available` API and requires no special feature flag.
//! It is the only function needed by ordinary code that runs in FIPS mode
//! but does not implement a FIPS provider itself.
//!
//! Items under `#[cfg(feature = "fips-provider")]` are only compiled when
//! the `fips-provider` cargo feature is enabled.  They require non-public
//! OpenSSL headers (vtable access, `prov_ctx` helpers, `ossl_prov_is_running`,
//! etc.) and are only meaningful when you are implementing a FIPS provider.

use native_ossl_sys as sys;
use std::sync::Arc;

/// Returns `true` if the FIPS provider is loaded and available in the given
/// library context.
///
/// Pass `None` to query the default (process-wide) library context.
///
/// Uses `OSSL_PROVIDER_available` — no `fips-provider` feature required.
///
/// # Example
///
/// ```ignore
/// use native_ossl::fips;
/// if fips::is_running(None) {
///     println!("Running in FIPS mode");
/// }
/// ```
#[must_use]
pub fn is_running(libctx: Option<&Arc<crate::lib_ctx::LibCtx>>) -> bool {
    let lctx = libctx.map_or(std::ptr::null_mut(), |c| c.as_ptr());
    unsafe { sys::OSSL_PROVIDER_available(lctx, c"fips".as_ptr()) != 0 }
}

// ── fips-provider — internal provider APIs ────────────────────────────────────

#[cfg(feature = "fips-provider")]
pub(crate) mod provider_impl {
    use crate::error::ErrorStack;
    use native_ossl_sys as sys;
    use native_ossl_sys::fips_internal as fips_sys;
    use std::ffi::{c_char, c_int, c_uchar, c_void, CStr};
    use std::ptr::null;

    // ── Hand-written EVP_SIGNATURE vtable layout ──────────────────────────────
    //
    // bindgen cannot generate the non-opaque `evp_signature_st` struct because
    // it contains `CRYPTO_REF_COUNT` which uses `_Atomic int` — a C11 feature
    // that bindgen does not support.
    //
    // We define the layout manually, verified against the OpenSSL source tree
    // using offsetof() in a C test program (see build notes).
    //
    // Platform: x86_64 Linux, OpenSSL 3.5.x
    // sizeof(evp_signature_st)     = 296
    // offsetof(newctx)             = 40
    // offsetof(digest_sign_init)   = 144
    // offsetof(digest_sign_update) = 152
    // offsetof(digest_sign_final)  = 160
    // offsetof(digest_sign)        = 168
    // offsetof(digest_verify_init) = 176
    // offsetof(digest_verify_update) = 184
    // offsetof(digest_verify_final) = 192
    // offsetof(digest_verify)      = 200
    // offsetof(freectx)            = 208

    type FnNewctx = unsafe extern "C" fn(*mut c_void, *const c_char) -> *mut c_void;
    type FnDigestSignInit = unsafe extern "C" fn(
        *mut c_void,
        *const c_char,
        *mut c_void,
        *const sys::OSSL_PARAM,
    ) -> c_int;
    type FnDigestSignUpdate = unsafe extern "C" fn(*mut c_void, *const c_uchar, usize) -> c_int;
    type FnDigestSignFinal =
        unsafe extern "C" fn(*mut c_void, *mut c_uchar, *mut usize, usize) -> c_int;
    type FnDigestSign = unsafe extern "C" fn(
        *mut c_void,
        *mut c_uchar,
        *mut usize,
        usize,
        *const c_uchar,
        usize,
    ) -> c_int;
    type FnDigestVerifyInit = unsafe extern "C" fn(
        *mut c_void,
        *const c_char,
        *mut c_void,
        *const sys::OSSL_PARAM,
    ) -> c_int;
    type FnDigestVerifyUpdate = unsafe extern "C" fn(*mut c_void, *const c_uchar, usize) -> c_int;
    type FnDigestVerifyFinal = unsafe extern "C" fn(*mut c_void, *const c_uchar, usize) -> c_int;
    type FnDigestVerify =
        unsafe extern "C" fn(*mut c_void, *const c_uchar, usize, *const c_uchar, usize) -> c_int;
    type FnFreectx = unsafe extern "C" fn(*mut c_void);

    /// Mirror of `evp_signature_st` with only the function pointer fields we
    /// use, padded to match the actual C layout.
    ///
    /// Field offsets (bytes, x86_64 Linux, OpenSSL 3.5.x):
    /// - `_header`       [0, 40)  — name_id, type_name, description, prov, refcnt + pad
    /// - `newctx`        [40, 48) — `OSSL_FUNC_signature_newctx_fn*`
    /// - `_skip_a`       [48, 144) — sign_init/sign, sign_message_*, verify_init/verify,
    ///                               verify_message_*, verify_recover_*
    /// - `digest_sign_init`   [144, 152)
    /// - `digest_sign_update` [152, 160)
    /// - `digest_sign_final`  [160, 168)
    /// - `digest_sign`        [168, 176)
    /// - `digest_verify_init` [176, 184)
    /// - `digest_verify_update` [184, 192)
    /// - `digest_verify_final`  [192, 200)
    /// - `digest_verify`        [200, 208)
    /// - `freectx`             [208, 216)
    /// - `_tail`               [216, 296)
    #[repr(C)]
    struct EvpSignatureVtable {
        _header: [u8; 40],
        newctx: Option<FnNewctx>,
        _skip_a: [u8; 96], // 48..144
        digest_sign_init: Option<FnDigestSignInit>,
        digest_sign_update: Option<FnDigestSignUpdate>,
        digest_sign_final: Option<FnDigestSignFinal>,
        digest_sign: Option<FnDigestSign>,
        digest_verify_init: Option<FnDigestVerifyInit>,
        digest_verify_update: Option<FnDigestVerifyUpdate>,
        digest_verify_final: Option<FnDigestVerifyFinal>,
        digest_verify: Option<FnDigestVerify>,
        freectx: Option<FnFreectx>,
        _tail: [u8; 80], // 216..296
    }

    // Compile-time assertion that the struct has the right total size.
    const _: () = assert!(
        std::mem::size_of::<EvpSignatureVtable>() == 296,
        "EvpSignatureVtable layout does not match evp_signature_st (expected 296 bytes)"
    );

    // ── Provider state helpers ────────────────────────────────────────────────

    /// Returns `true` if the FIPS module is in a running (non-error) state.
    ///
    /// Calls `ossl_prov_is_running()`.  Only meaningful inside a FIPS provider
    /// implementation.
    pub fn check_state_ok() -> bool {
        unsafe { fips_sys::ossl_prov_is_running() != 0 }
    }

    /// Signal a FIPS self-test failure.
    ///
    /// Calls `ossl_set_error_state(OSSL_SELF_TEST_TYPE_PCT)`.  This puts the
    /// FIPS module into the error state; any subsequent `check_state_ok()` call
    /// will return `false`.
    pub fn set_error_state() {
        unsafe {
            fips_sys::ossl_set_error_state(
                fips_sys::OSSL_SELF_TEST_TYPE_PCT.as_ptr() as *const c_char
            );
        }
    }

    // ── PROV_CTX helpers ─────────────────────────────────────────────────────

    /// Allocate a new `PROV_CTX`.
    ///
    /// # Safety
    /// The returned pointer must be freed with [`prov_ctx_free`].
    pub unsafe fn prov_ctx_new() -> *mut fips_sys::prov_ctx_st {
        fips_sys::ossl_prov_ctx_new()
    }

    /// Free a `PROV_CTX` allocated by [`prov_ctx_new`].
    ///
    /// # Safety
    /// `ctx` must be a valid pointer returned by `prov_ctx_new`.
    pub unsafe fn prov_ctx_free(ctx: *mut fips_sys::prov_ctx_st) {
        fips_sys::ossl_prov_ctx_free(ctx);
    }

    /// Store the `OSSL_CORE_HANDLE` into a `PROV_CTX`.
    ///
    /// The handle is the opaque `fips_internal::OSSL_CORE_HANDLE` type, which
    /// the caller receives as the first argument of `OSSL_provider_init_int`.
    ///
    /// # Safety
    /// Both pointers must be valid.
    pub unsafe fn prov_ctx_set_handle(
        ctx: *mut fips_sys::prov_ctx_st,
        handle: *const fips_sys::OSSL_CORE_HANDLE,
    ) {
        fips_sys::ossl_prov_ctx_set0_handle(ctx, handle);
    }

    /// Store a raw `OSSL_LIB_CTX` pointer into a `PROV_CTX`.
    ///
    /// The `libctx` pointer is accepted as `*mut c_void` to allow passing a
    /// pointer obtained from `OSSL_CORE_HANDLE` dispatch functions without
    /// having to depend on the exact (opaque) `OSSL_LIB_CTX` type.
    ///
    /// # Safety
    /// Both pointers must be valid.
    pub unsafe fn prov_ctx_set_libctx(ctx: *mut fips_sys::prov_ctx_st, libctx: *mut c_void) {
        fips_sys::ossl_prov_ctx_set0_libctx(ctx, libctx as *mut fips_sys::OSSL_LIB_CTX);
    }

    /// Retrieve the `OSSL_LIB_CTX` stored in a `PROV_CTX` as a raw `*mut c_void`.
    ///
    /// Cast the result to `*mut native_ossl_sys::OSSL_LIB_CTX` to use it with
    /// native-ossl's `LibCtx::from_ptr`.
    ///
    /// # Safety
    /// `ctx` must be a valid pointer.
    pub unsafe fn prov_ctx_get_libctx(ctx: *mut fips_sys::prov_ctx_st) -> *mut c_void {
        fips_sys::ossl_prov_ctx_get0_libctx(ctx) as *mut c_void
    }

    // ── ProviderSignatureCtx ──────────────────────────────────────────────────

    /// Vtable-based signature context.
    ///
    /// Direct access to `EVP_SIGNATURE` function pointers, needed inside a FIPS
    /// provider where `EVP_DigestSign*` is unavailable (circular provider
    /// dependency).
    ///
    /// # Lifecycle
    ///
    /// 1. Call [`ProviderSignatureCtx::new`] with the library context and the
    ///    provider's own `provctx` (obtained during `OSSL_provider_init_int`).
    /// 2. Call [`digest_sign_init`] / [`digest_verify_init`] to bind the key.
    /// 3. Call `update` and `final` (or `digest_sign` / `digest_verify` for
    ///    one-shot operations).
    ///
    /// [`digest_sign_init`]: ProviderSignatureCtx::digest_sign_init
    /// [`digest_verify_init`]: ProviderSignatureCtx::digest_verify_init
    pub struct ProviderSignatureCtx {
        vtable: *mut sys::EVP_SIGNATURE,
        ctx: *mut c_void,
    }

    impl ProviderSignatureCtx {
        /// Fetch the named signature algorithm and create a provider-side context.
        ///
        /// - `libctx` — the `OSSL_LIB_CTX` used to fetch the algorithm.  Cast
        ///   from a `*mut c_void` to allow passing the value from provider init
        ///   dispatch functions without depending on the opaque `OSSL_LIB_CTX` type.
        /// - `provctx` — the provider's own context (the `void *provctx` received
        ///   during `OSSL_provider_init_int`); passed to the `newctx` vtable fn.
        /// - `alg_name` — algorithm name, e.g. `c"ECDSA"`, `c"RSA"`, `c"EDDSA"`.
        pub fn new(
            libctx: *mut c_void,
            provctx: *mut c_void,
            alg_name: &CStr,
        ) -> Result<Self, ErrorStack> {
            // SAFETY: libctx is a valid OSSL_LIB_CTX* or null (global context);
            // alg_name is a valid NUL-terminated C string from &CStr.
            let vtable_opaque = unsafe {
                sys::EVP_SIGNATURE_fetch(
                    libctx as *mut sys::OSSL_LIB_CTX,
                    alg_name.as_ptr(),
                    null(),
                )
            };
            if vtable_opaque.is_null() {
                return Err(ErrorStack::drain());
            }

            // Cast to our hand-written vtable struct.
            let vtable = vtable_opaque as *mut EvpSignatureVtable;

            // SAFETY: vtable is a non-null EVP_SIGNATURE* just fetched above, cast
            // to our hand-verified EvpSignatureVtable layout (size asserted at compile
            // time).  provctx is the provider context passed in by the caller.
            let ctx = unsafe {
                match (*vtable).newctx {
                    Some(f) => f(provctx, null()),
                    None => {
                        sys::EVP_SIGNATURE_free(vtable_opaque);
                        return Err(ErrorStack::drain());
                    }
                }
            };
            if ctx.is_null() {
                unsafe { sys::EVP_SIGNATURE_free(vtable_opaque) };
                return Err(ErrorStack::drain());
            }

            Ok(ProviderSignatureCtx {
                vtable: vtable_opaque,
                ctx,
            })
        }

        fn vtable(&self) -> *mut EvpSignatureVtable {
            self.vtable as *mut EvpSignatureVtable
        }

        // ── Signing ──────────────────────────────────────────────────────────

        /// Initialise for digest-sign.
        ///
        /// - `md_name` — digest algorithm name; may be null for implicit-digest
        ///   algorithms.
        /// - `keydata` — provider-side key data.  For a `Pkey<Private>`, obtain
        ///   this via `pkey.keydata()` (requires the `fips-provider` feature).
        /// - `params` — optional additional parameters (pass `null()` for defaults).
        pub fn digest_sign_init(
            &mut self,
            md_name: *const c_char,
            keydata: *mut c_void,
            params: *const sys::OSSL_PARAM,
        ) -> Result<(), ErrorStack> {
            // SAFETY: self.vtable is non-null (set in new() and never changed).
            // The vtable layout is verified by the size assertion.  self.ctx is
            // a valid provider-side context created by newctx and not yet freed.
            unsafe {
                match (*self.vtable()).digest_sign_init {
                    Some(f) => {
                        if f(self.ctx, md_name, keydata, params) != 1 {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(())
        }

        /// Feed data to the digest-sign operation.
        pub fn digest_sign_update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            // data.as_ptr()/len() are valid for the slice lifetime.
            unsafe {
                match (*self.vtable()).digest_sign_update {
                    Some(f) => {
                        if f(self.ctx, data.as_ptr() as *const c_uchar, data.len()) != 1 {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(())
        }

        /// Finalise digest-sign and write into `signature`.
        ///
        /// Returns the number of bytes written.
        pub fn digest_sign_final(&mut self, signature: &mut [u8]) -> Result<usize, ErrorStack> {
            let mut siglen: usize = signature.len();
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            // signature is a valid mutable slice; siglen is initialised to its length.
            unsafe {
                match (*self.vtable()).digest_sign_final {
                    Some(f) => {
                        if f(
                            self.ctx,
                            signature.as_mut_ptr() as *mut c_uchar,
                            &mut siglen,
                            signature.len(),
                        ) != 1
                        {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(siglen)
        }

        /// One-shot digest-sign.
        ///
        /// Pass `None` for `signature` to query the required buffer size.
        /// Returns the number of bytes written (or required when `signature` is `None`).
        pub fn digest_sign(
            &mut self,
            mut signature: Option<&mut [u8]>,
            tbs: &[u8],
        ) -> Result<usize, ErrorStack> {
            let (sigptr, sigcap) = match &mut signature {
                Some(s) => (s.as_mut_ptr() as *mut c_uchar, s.len()),
                None => (std::ptr::null_mut(), 0usize),
            };
            let mut siglen = sigcap;
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            // sigptr is either null (size-query path) or a valid mutable buffer.
            // tbs.as_ptr()/len() are valid for the slice lifetime.
            unsafe {
                match (*self.vtable()).digest_sign {
                    Some(f) => {
                        if f(
                            self.ctx,
                            sigptr,
                            &mut siglen,
                            sigcap,
                            tbs.as_ptr() as *const c_uchar,
                            tbs.len(),
                        ) != 1
                        {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(siglen)
        }

        // ── Verification ─────────────────────────────────────────────────────

        /// Initialise for digest-verify.
        pub fn digest_verify_init(
            &mut self,
            md_name: *const c_char,
            keydata: *mut c_void,
            params: *const sys::OSSL_PARAM,
        ) -> Result<(), ErrorStack> {
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            unsafe {
                match (*self.vtable()).digest_verify_init {
                    Some(f) => {
                        if f(self.ctx, md_name, keydata, params) != 1 {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(())
        }

        /// Feed data to the digest-verify operation.
        pub fn digest_verify_update(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            // data.as_ptr()/len() are valid for the slice lifetime.
            unsafe {
                match (*self.vtable()).digest_verify_update {
                    Some(f) => {
                        if f(self.ctx, data.as_ptr() as *const c_uchar, data.len()) != 1 {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(())
        }

        /// Finalise digest-verify.  Returns `Ok(())` if the signature is valid.
        pub fn digest_verify_final(&mut self, signature: &[u8]) -> Result<(), ErrorStack> {
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            // signature.as_ptr()/len() are valid for the slice lifetime.
            unsafe {
                match (*self.vtable()).digest_verify_final {
                    Some(f) => {
                        if f(
                            self.ctx,
                            signature.as_ptr() as *const c_uchar,
                            signature.len(),
                        ) != 1
                        {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(())
        }

        /// One-shot digest-verify.  Returns `Ok(())` if the signature is valid.
        pub fn digest_verify(&mut self, signature: &[u8], tbs: &[u8]) -> Result<(), ErrorStack> {
            // SAFETY: self.vtable and self.ctx are valid (see digest_sign_init).
            // Both slices' as_ptr()/len() are valid for their respective lifetimes.
            unsafe {
                match (*self.vtable()).digest_verify {
                    Some(f) => {
                        if f(
                            self.ctx,
                            signature.as_ptr() as *const c_uchar,
                            signature.len(),
                            tbs.as_ptr() as *const c_uchar,
                            tbs.len(),
                        ) != 1
                        {
                            return Err(ErrorStack::drain());
                        }
                    }
                    None => return Err(ErrorStack::drain()),
                }
            }
            Ok(())
        }
    }

    impl Drop for ProviderSignatureCtx {
        fn drop(&mut self) {
            // Call freectx first (provider frees its own context), then release
            // the EVP_SIGNATURE vtable reference.  EVP_SIGNATURE_free must always
            // run, so it is placed after the freectx call rather than inside the
            // same unsafe block to ensure it is not skipped by a hypothetical panic
            // in a non-conforming provider.
            if !self.ctx.is_null() {
                // SAFETY: self.vtable is non-null and self.ctx is the context
                // allocated by newctx; freectx is the matching destructor.
                unsafe {
                    let vt = self.vtable as *mut EvpSignatureVtable;
                    if let Some(f) = (*vt).freectx {
                        f(self.ctx);
                    }
                }
            }
            if !self.vtable.is_null() {
                // SAFETY: self.vtable was obtained from EVP_SIGNATURE_fetch and
                // has not been freed yet.
                unsafe { sys::EVP_SIGNATURE_free(self.vtable) };
            }
        }
    }

    // SAFETY: `EVP_SIGNATURE*` is reference-counted and the vtable is read-only
    // after the fetch.  `ctx` is a per-operation mutable C context; it is safe to
    // move across threads (Send) but NOT to access concurrently from multiple
    // threads without external synchronisation (not Sync).
    unsafe impl Send for ProviderSignatureCtx {}
}

#[cfg(feature = "fips-provider")]
pub use provider_impl::{
    check_state_ok, prov_ctx_free, prov_ctx_get_libctx, prov_ctx_new, prov_ctx_set_handle,
    prov_ctx_set_libctx, set_error_state, ProviderSignatureCtx,
};

// Re-export the prov_ctx_st type alias so callers can name it.
#[cfg(feature = "fips-provider")]
pub use native_ossl_sys::fips_internal::{prov_ctx_st as ProvCtx, OSSL_CORE_HANDLE};
