//! Types pertaining to registering a plugin implementation and handling plugin
//! authenticator requests.

#![allow(non_snake_case)]
#![allow(non_camel_case_types)]

use std::{mem::MaybeUninit, num::NonZeroU32, ptr::NonNull};

use base64::{engine::general_purpose::STANDARD, Engine as _};
use windows::{
    core::{GUID, HRESULT},
    Win32::{Foundation::HWND, System::Com::CoTaskMemFree},
};
use windows_core::BOOL;

use super::Clsid;
use crate::{
    plugin::crypto,
    types::{
        AuthenticatorInfo, CredentialEx, CtapTransport, HmacSecretSalt, RpEntityInformation,
        UserEntityInformation, UserId, WebAuthnExtensionMakeCredentialOutput,
        WEBAUTHN_COSE_CREDENTIAL_PARAMETER, WEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
        WEBAUTHN_CREDENTIAL_ATTESTATION, WEBAUTHN_CREDENTIAL_LIST, WEBAUTHN_EXTENSIONS,
        WEBAUTHN_RP_ENTITY_INFORMATION, WEBAUTHN_USER_ENTITY_INFORMATION,
    },
    util::{webauthn_call, WindowsString},
    CredentialId, ErrorKind, WinWebAuthnError,
};

// Plugin Registration types

/// Windows WebAuthn Authenticator Options structure
/// Header File Name: _WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS {
    dwVersion: u32,
    // LONG lUp: +1=TRUE, 0=Not defined, -1=FALSE
    lUp: i32,
    // LONG lUv: +1=TRUE, 0=Not defined, -1=FALSE
    lUv: i32,
    // LONG lRequireResidentKey: +1=TRUE, 0=Not defined, -1=FALSE
    lRequireResidentKey: i32,
}

impl WebAuthnCtapCborAuthenticatorOptions {
    pub fn version(&self) -> u32 {
        self.dwVersion
    }

    pub fn user_presence(&self) -> Option<bool> {
        Self::to_optional_bool(self.lUp)
    }

    pub fn user_verification(&self) -> Option<bool> {
        Self::to_optional_bool(self.lUv)
    }

    pub fn require_resident_key(&self) -> Option<bool> {
        Self::to_optional_bool(self.lRequireResidentKey)
    }

    fn to_optional_bool(value: i32) -> Option<bool> {
        match value {
            x if x > 0 => Some(true),
            x if x < 0 => Some(false),
            _ => None,
        }
    }
}

pub type WebAuthnCtapCborAuthenticatorOptions = WEBAUTHN_CTAPCBOR_AUTHENTICATOR_OPTIONS;

/// Used when adding a Windows plugin authenticator (stable API).
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS
/// Header File Usage: WebAuthNPluginAddAuthenticator()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS {
    /// Authenticator Name
    pub(super) pwszAuthenticatorName: *const u16,

    /// Plugin COM ClsId
    pub(super) rclsid: *const GUID,

    /// Plugin RPID
    ///
    /// Required for a nested WebAuthN call originating from a plugin.
    pub(super) pwszPluginRpId: *const u16,

    /// Plugin Authenticator Logo for the Light themes.  base64-encoded SVG 1.1
    ///
    /// The data should be encoded as `UTF16(BASE64(UTF8(svg_text)))`.
    pub(super) pwszLightThemeLogoSvg: *const u16,

    /// Plugin Authenticator Logo for the Dark themes.  base64-encoded SVG 1.1
    ///
    /// The data should be encoded as `UTF16(BASE64(UTF8(svg_text)))`.
    pub(super) pwszDarkThemeLogoSvg: *const u16,

    pub(super) cbAuthenticatorInfo: u32,
    /// CTAP CBOR-encoded authenticatorGetInfo output
    pub(super) pbAuthenticatorInfo: *const u8,

    pub(super) cSupportedRpIds: u32,
    /// List of supported RP IDs (Relying Party IDs).
    ///
    /// Should be null if all RPs are supported.
    pub(super) pbSupportedRpIds: *const *const u16,
}

pub struct PluginAddAuthenticatorOptions {
    /// Authenticator Name
    pub authenticator_name: String,

    /// Plugin COM ClsId
    pub clsid: Clsid,

    /// Plugin RPID
    ///
    /// Required for a nested WebAuthN call originating from a plugin.
    pub rp_id: Option<String>,

    /// Plugin Authenticator Logo for the Light themes.
    ///
    /// String should contain a valid SVG 1.1 document.
    pub light_theme_logo_svg: Option<String>,

    // Plugin Authenticator Logo for the Dark themes. Bytes of SVG 1.1.
    ///
    /// String should contain a valid SVG 1.1 element.
    pub dark_theme_logo_svg: Option<String>,

    /// CTAP authenticatorGetInfo values
    pub authenticator_info: AuthenticatorInfo,

    /// List of supported RP IDs (Relying Party IDs).
    ///
    /// Should be [None] if all RPs are supported.
    pub supported_rp_ids: Option<Vec<String>>,
}

impl PluginAddAuthenticatorOptions {
    pub(super) fn light_theme_logo_b64(&self) -> Option<Vec<u16>> {
        self.light_theme_logo_svg
            .as_ref()
            .map(|svg| Self::encode_svg(svg))
    }

    pub(super) fn dark_theme_logo_b64(&self) -> Option<Vec<u16>> {
        self.dark_theme_logo_svg
            .as_ref()
            .map(|svg| Self::encode_svg(svg))
    }

    fn encode_svg(svg: &str) -> Vec<u16> {
        let logo_b64: String = STANDARD.encode(svg);
        logo_b64.to_utf16()
    }
}

/// Used as a response type when adding a Windows plugin authenticator.
/// Header File Name: _WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
/// Header File Usage: WebAuthNPluginAddAuthenticator()
///                    WebAuthNPluginFreeAddAuthenticatorResponse()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WebAuthnPluginAddAuthenticatorResponse {
    /// Size in bytes of the public key pointed to by `pbOpSignPubKey`.
    cbOpSignPubKey: u32,
    /// Pointer to a [BCRYPT_KEY_BLOB](windows::Win32::Security::Cryptography::BCRYPT_KEY_BLOB).
    pbOpSignPubKey: *mut u8,
}

type WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE = WebAuthnPluginAddAuthenticatorResponse;

/// Response received when registering a plugin
#[derive(Debug)]
pub struct PluginAddAuthenticatorResponse {
    inner: NonNull<WebAuthnPluginAddAuthenticatorResponse>,
}

impl PluginAddAuthenticatorResponse {
    pub fn plugin_operation_signing_key(&self) -> &[u8] {
        // SAFETY: when constructed from Self::try_from_ptr(), the caller
        // ensures that Windows created the pointer, which we trust to create
        // valid responses.
        unsafe {
            std::slice::from_raw_parts(
                self.inner.as_ref().pbOpSignPubKey,
                // SAFETY: We only support 32-bit or 64-bit platforms, so u32 will always fit in
                // usize.
                self.inner.as_ref().cbOpSignPubKey as usize,
            )
        }
    }

    /// # Safety
    /// When calling this function, the caller must ensure that the pointer was
    /// initialized by a successful call to [webauthn_plugin_add_authenticator()].
    pub(super) unsafe fn try_from_ptr(
        value: NonNull<WebAuthnPluginAddAuthenticatorResponse>,
    ) -> Self {
        Self { inner: value }
    }
}

impl Drop for PluginAddAuthenticatorResponse {
    fn drop(&mut self) {
        unsafe {
            // SAFETY: This should only fail if:
            // - we cannot load the webauthn.dll, which we already have if we have constructed this
            //   type, or
            // - we spelled the function wrong, which is a library error.
            webauthn_plugin_free_add_authenticator_response(self.inner.as_mut())
                .expect("function to load properly");
        }
    }
}

webauthn_call!("WebAuthNPluginAddAuthenticator" as
/// Register authenticator info for a plugin COM server.
/// 
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
/// 
/// # Arguments
/// - `pPluginAddAuthenticatorOptions`: Details about the authenticator to set.
/// - `ppPluginAddAuthenticatorResponse`:
///    An indirect pointer to a [WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE], which will be written to on success.
///    If the request succeeds, the data must be freed by a call to [webauthn_plugin_free_add_authenticator_response].
fn webauthn_plugin_add_authenticator(
    pPluginAddAuthenticatorOptions: *const WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS,
    ppPluginAddAuthenticatorResponse: *mut *mut WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE
) -> HRESULT);

webauthn_call!("WebAuthNPluginFreeAddAuthenticatorResponse" as
/// Free memory from a [WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE].
/// 
/// # Arguments
/// - `pPluginAddAuthenticatorResponse`: An pointer to a [WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE] to be freed.
fn webauthn_plugin_free_add_authenticator_response(
    pPluginAddAuthenticatorResponse: *mut WebAuthnPluginAddAuthenticatorResponse
) -> ());

// Credential syncing types

/// Represents a credential.
/// Header File Name: _WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
/// Header File Usage: WebAuthNPluginAuthenticatorAddCredentials, etc.
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS {
    pub credential_id_byte_count: u32,
    pub credential_id_pointer: *const u8,
    pub rpid: *const u16,
    pub rp_friendly_name: *const u16,
    pub user_id_byte_count: u32,
    pub user_id_pointer: *const u8,
    pub user_name: *const u16,
    pub user_display_name: *const u16,
}

/// Credential metadata to sync to Windows Hello credential autofill list.
#[derive(Debug)]
pub struct PluginCredentialDetails {
    /// Credential ID.
    pub credential_id: CredentialId,

    /// Relying party ID.
    pub rp_id: String,

    /// Relying party display name.
    pub rp_friendly_name: Option<String>,

    /// User handle.
    pub user_id: UserId,

    /// User name.
    ///
    /// Corresponds to [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialentity-name) field of WebAuthn `PublicKeyCredentialUserEntity`.
    pub user_name: String,

    /// User name.
    ///
    /// Corresponds to [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentity-displayname) field of WebAuthn `PublicKeyCredentialUserEntity`.
    pub user_display_name: String,
}

webauthn_call!("WebAuthNPluginAuthenticatorAddCredentials" as
/// Add metadata for a list of WebAuthn credentials to the autofill store for
/// this plugin authenticator.
/// 
/// This will make the credentials available for discovery in Windows Hello
/// WebAuthn autofill dialogs.
///
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
/// 
/// # Arguments
/// - `rclsid`: The CLSID corresponding to this plugin's COM server.
/// - `cCredentialDetails`: The number of credentials in the array pointed to by `pCredentialDetails`.
/// - `pCredentialDetails`: An array of credential metadata.
fn webauthn_plugin_authenticator_add_credentials(
    rclsid: *const GUID,
    cCredentialDetails: u32,
    pCredentialDetails: *const WEBAUTHN_PLUGIN_CREDENTIAL_DETAILS
) -> HRESULT);

webauthn_call!("WebAuthNPluginAuthenticatorRemoveAllCredentials" as
/// Removes metadata for all credentials currently stored in the autofill store
/// for this plugin authenticator.
/// 
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
/// 
/// # Arguments
/// - `rclsid`: The CLSID corresponding to this plugin's COM server.
fn webauthn_plugin_authenticator_remove_all_credentials(rclsid: *const GUID) -> HRESULT);

#[repr(C)]
#[derive(Debug)]
pub(super) struct WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST {
    /// Windows handle of the top-level window displayed by the plugin and
    /// currently is in foreground as part of the ongoing WebAuthn operation.
    pub(super) hwnd: HWND,

    /// The WebAuthn transaction id from the WEBAUTHN_PLUGIN_OPERATION_REQUEST
    pub(super) rguidTransactionId: *const GUID,

    /// The username attached to the credential that is in use for this WebAuthn
    /// operation.
    pub(super) pwszUsername: *const u16,

    /// A text hint displayed on the Windows Hello prompt.
    pub(super) pwszDisplayHint: *const u16,
}

#[derive(Debug)]
pub struct PluginUserVerificationRequest {
    /// Windows handle of the top-level window displayed by the plugin and
    /// currently is in foreground as part of the ongoing WebAuthn operation.
    pub window_handle: HWND,

    /// The WebAuthn transaction id from the WEBAUTHN_PLUGIN_OPERATION_REQUEST
    pub transaction_id: GUID,

    /// The username attached to the credential that is in use for this WebAuthn
    /// operation.
    pub user_name: String,

    /// A text hint displayed on the Windows Hello prompt.
    pub display_hint: Option<String>,
}

/// Response details from user verification.
pub struct PluginUserVerificationResponse {
    pub transaction_id: GUID,
    /// Bytes of the signature over the response.
    pub signature: Vec<u8>,
}

webauthn_call!("WebAuthNPluginPerformUserVerification" as
/// Request user verification for a WebAuthn operation.
/// 
/// The OS will prompt the user for verification, and if the user is
/// successfully verified, will write a signature to `ppbResponse`, which must
/// be freed by a call to [webauthn_plugin_free_user_verification_response].
/// 
/// The signature is over the SHA-256 hash of the original WebAuthn operation request buffer
/// corresponding to `pPluginUserVerification.rguidTransactionId`. It can be
/// verified using the user verification public key, which can be retrieved
/// using
/// [webauthn_plugin_get_user_verification_public_key][crate::plugin::crypto::webauthn_plugin_get_user_verification_public_key].
///
/// This request will block while the user interacts with the dialog.
///
/// # Arguments
/// - `pPluginUserVerification`: The user verification prompt and transaction context for the request.
/// - `pcbResponse`: Length in bytes of the signature.
/// - `ppbResponse`: The signature of the request.
fn webauthn_plugin_perform_user_verification(
    pPluginUserVerification: *const WEBAUTHN_PLUGIN_USER_VERIFICATION_REQUEST,
    pcbResponse: *mut u32,
    ppbResponse: *mut *mut u8
) -> HRESULT);

webauthn_call!("WebAuthNPluginFreeUserVerificationResponse" as
/// Free a user verification response received from a call to [webauthn_plugin_perform_user_verification].
fn webauthn_plugin_free_user_verification_response(
    pbResponse: *mut u8
) -> ());

// Plugin Authenticator types

/// Used when creating and asserting credentials.
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_REQUEST
/// Header File Usage: MakeCredential()
///                    GetAssertion()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_PLUGIN_OPERATION_REQUEST {
    /// Window handle to client that requesting a WebAuthn credential.
    pub hWnd: HWND,
    pub transactionId: GUID,
    pub cbRequestSignature: u32,
    /// Signature over request made with the signing key created during authenticator registration.
    pub pbRequestSignature: *mut u8,
    pub requestType: WebAuthnPluginRequestType,
    pub cbEncodedRequest: u32,
    pub pbEncodedRequest: *const u8,
}

/// Used as a response when creating and asserting credentials.
/// Header File Name: _WEBAUTHN_PLUGIN_OPERATION_RESPONSE
/// Header File Usage: MakeCredential()
///                    GetAssertion()
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(crate) struct WEBAUTHN_PLUGIN_OPERATION_RESPONSE {
    pub cbEncodedResponse: u32,
    pub pbEncodedResponse: *mut u8,
}

/// Plugin request type enum as defined in the IDL
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum WebAuthnPluginRequestType {
    // This is being used to check the value that Windows gives us, but it isn't
    // ever constructed by our library.
    #[allow(unused)]
    CTAP2_CBOR = 0x01,
}

// MakeCredential types

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
    pub dwVersion: u32,
    pub cbRpId: u32,
    pub pbRpId: *const u8,
    pub cbClientDataHash: u32,
    pub pbClientDataHash: *const u8,
    pub pRpInformation: *const WEBAUTHN_RP_ENTITY_INFORMATION,
    pub pUserInformation: *const WEBAUTHN_USER_ENTITY_INFORMATION,
    pub WebAuthNCredentialParameters: WEBAUTHN_COSE_CREDENTIAL_PARAMETERS,
    pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
    pub cbCborExtensionsMap: u32,
    pub pbCborExtensionsMap: *const u8,
    pub pAuthenticatorOptions: *const WebAuthnCtapCborAuthenticatorOptions,

    // Pin Auth (Optional)
    /// Indicates zero length PinAuth is included in the request
    pub fEmptyPinAuth: BOOL,
    pub cbPinAuth: u32,
    pub pbPinAuth: *const u8,

    /// "hmac-secret": true extension
    pub lHmacSecretExt: i32,

    /// "hmac-secret-mc" extension
    pub pHmacSecretMcExtension: *const WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION,

    /// "prf" extension
    pub lPrfExt: i32,
    pub cbHmacSecretSaltValues: u32,
    pub pbHmacSecretSaltValues: *const u8,

    /// "credProtect" extension. Nonzero if present
    pub dwCredProtect: Option<NonZeroU32>,

    /// Nonzero if present
    pub dwPinProtocol: Option<NonZeroU32>,

    /// Nonzero if present
    pub dwEnterpriseAttestation: Option<NonZeroU32>,

    /// "credBlob" extension. Nonzero if present
    pub cbCredBlobExt: Option<NonZeroU32>,
    pub pbCredBlobExt: *const u8,

    /// "largeBlobKey": true extension
    pub lLargeBlobKeyExt: i32,

    /// "largeBlob": extension
    pub dwLargeBlobSupport: u32,

    /// "minPinLength": true extension
    pub lMinPinLengthExt: i32,

    /// "json" extension. Nonzero if present
    pub cbJsonExt: u32,
    pub pbJsonExt: *const u8,
}

#[derive(Debug)]
pub struct PluginMakeCredentialRequest {
    inner: *const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
    pub window_handle: HWND,
    pub transaction_id: GUID,
    pub request_signature: Vec<u8>,
    /// SHA-256 hash of the request.
    ///
    /// Can be used to verify the request later, for example in associated
    /// prompts for user verification.
    pub request_hash: Vec<u8>,
}

impl PluginMakeCredentialRequest {
    pub fn client_data_hash(&self) -> &[u8] {
        // SAFETY: clientDataHash is a required field, and when this is
        // constructed using Self::try_from_ptr(), the Windows decode API
        // constructs valid pointers.
        unsafe {
            std::slice::from_raw_parts(
                self.as_ref().pbClientDataHash,
                // SAFETY: we only support Windows versions where usize >= 32
                self.as_ref().cbClientDataHash as usize,
            )
        }
    }

    pub fn rp_information(&self) -> RpEntityInformation<'_> {
        let ptr = self.as_ref().pRpInformation;
        // SAFETY: When this is constructed using Self::try_from_ptr(), the caller must ensure that
        // pRpInformation is valid.
        unsafe { RpEntityInformation::new(ptr.as_ref().expect("pRpInformation to be non-null")) }
    }

    pub fn user_information(&self) -> UserEntityInformation<'_> {
        // SAFETY: When this is constructed using Self::try_from_ptr(), the Windows decode API
        // constructs valid pointers.
        let ptr = self.as_ref().pUserInformation;
        assert!(!ptr.is_null());
        unsafe {
            UserEntityInformation::new(ptr.as_ref().expect("pUserInformation to be non-null"))
        }
    }

    pub fn pub_key_cred_params(&self) -> impl Iterator<Item = &WEBAUTHN_COSE_CREDENTIAL_PARAMETER> {
        // SAFETY: When this is constructed from Self::try_from_ptr(), the Windows decode API
        // constructs valid pointers.
        unsafe { self.as_ref().WebAuthNCredentialParameters.iter() }
    }

    pub fn exclude_credentials(&self) -> impl Iterator<Item = CredentialEx<'_>> {
        // SAFETY: When this is constructed from Self::try_from_ptr(), the Windows decode API
        // constructs valid pointers.
        unsafe { self.as_ref().CredentialList.iter() }
    }

    /// CTAP CBOR extensions map
    pub fn extensions(&self) -> Option<&[u8]> {
        let (len, ptr) = (
            self.as_ref().cbCborExtensionsMap,
            self.as_ref().pbCborExtensionsMap,
        );
        if len == 0 || ptr.is_null() {
            return None;
        }
        unsafe { Some(std::slice::from_raw_parts(ptr, len as usize)) }
    }

    pub fn authenticator_options(&self) -> Option<WebAuthnCtapCborAuthenticatorOptions> {
        let ptr = self.as_ref().pAuthenticatorOptions;
        if ptr.is_null() {
            return None;
        }
        unsafe { Some(*ptr) }
    }

    /// # Safety
    /// When calling this method, callers must ensure:
    /// - `ptr` must be convertible to a reference.
    /// - `ptr` must have been allocated by Windows COM
    /// - pbEncodedRequest must be non-null and have the length specified in cbEncodedRequest.
    /// - pbRequestSignature must be non-null and have the length specified in cbRequestSignature.
    pub(super) unsafe fn try_from_ptr(
        ptr: NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>,
    ) -> Result<PluginMakeCredentialRequest, WinWebAuthnError> {
        let request = ptr.as_ref();
        if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) {
            return Err(WinWebAuthnError::new(
                ErrorKind::Serialization,
                "Unknown plugin operation request type",
            ));
        }
        let request_slice =
            std::slice::from_raw_parts(request.pbEncodedRequest, request.cbEncodedRequest as usize);
        let request_hash = crypto::hash_sha256(request_slice).map_err(|err| {
            WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "failed to hash request", err)
        })?;
        let mut registration_request = MaybeUninit::uninit();
        webauthn_decode_make_credential_request(
            request.cbEncodedRequest,
            request.pbEncodedRequest,
            registration_request.as_mut_ptr(),
        )?
        .ok()
        .map_err(|err| {
            WinWebAuthnError::with_cause(
                ErrorKind::WindowsInternal,
                "Failed to decode get assertion request",
                err,
            )
        })?;
        // SAFETY: Initialized by successful call to webauthn_decode_make_credential()
        let registration_request = registration_request.assume_init();

        if request.hWnd.is_invalid() {
            return Err(WinWebAuthnError::new(
                ErrorKind::WindowsInternal,
                "Invalid handle received",
            ));
        }

        Ok(Self {
            inner: registration_request as *const WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
            window_handle: request.hWnd,
            transaction_id: request.transactionId,
            request_signature: std::slice::from_raw_parts(
                request.pbRequestSignature,
                request.cbRequestSignature as usize,
            )
            .to_vec(),
            request_hash,
        })
    }
}

impl AsRef<WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST> for PluginMakeCredentialRequest {
    fn as_ref(&self) -> &WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST {
        unsafe { &*self.inner }
    }
}

impl Drop for PluginMakeCredentialRequest {
    fn drop(&mut self) {
        if !self.inner.is_null() {
            // SAFETY: the caller is responsible for ensuring that this pointer
            // is allocated with an allocator corresponding to this free
            // function.
            unsafe {
                // leak memory if we cannot find the free function
                _ = webauthn_free_decoded_make_credential_request(
                    self.inner as *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST,
                );
            }
        }
    }
}

// Windows API function signatures for decoding make credential requests
webauthn_call!("WebAuthNDecodeMakeCredentialRequest" as
/// Decodes a CTAP CBOR `authenticatorMakeCredential` request.
///
/// On success, a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] will be written to
/// `ppMakeCredentialRequest`, which must be freed by a call to
/// [webauthn_free_decoded_make_credential_request].
/// 
/// # Arguments
/// - `pbEncoded`: a COM-allocated buffer pointing to a CTAP CBOR make credential request.
/// - `ppMakeCredentialRequest`: An indirect pointer to a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST].
/// 
/// # Safety
/// - `pbEncoded` must have been allocated by Windows COM.
/// - `pbEncoded` must be non-null and have the length specified in cbEncoded.
fn webauthn_decode_make_credential_request(
    cbEncoded: u32,
    pbEncoded: *const u8,
    ppMakeCredentialRequest: *mut *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
) -> HRESULT);

webauthn_call!("WebAuthNFreeDecodedMakeCredentialRequest" as
/// Frees a decoded make credential request from [webauthn_free_decoded_make_credential_request].
/// 
/// # Arguments
/// - `pMakeCredentialRequest`: An pointer to a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] to be freed.
fn webauthn_free_decoded_make_credential_request(
    pMakeCredentialRequest: *mut WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST
) -> ());

pub struct PluginMakeCredentialResponse {
    /// Attestation format type
    pub format_type: String, // PCWSTR

    /// Authenticator data that was created for this credential.
    pub authenticator_data: Vec<u8>,

    ///Encoded CBOR attestation information
    pub attestation_statement: Option<Vec<u8>>,

    // dwAttestationDecodeType: u32,
    /// Following depends on the dwAttestationDecodeType
    ///  WEBAUTHN_ATTESTATION_DECODE_NONE
    ///      NULL - not able to decode the CBOR attestation information
    ///  WEBAUTHN_ATTESTATION_DECODE_COMMON
    ///      PWEBAUTHN_COMMON_ATTESTATION;
    // pub pvAttestationDecode: *mut u8,

    /// The CBOR-encoded Attestation Object to be returned to the RP.
    pub attestation_object: Option<Vec<u8>>,

    /// The CredentialId bytes extracted from the Authenticator Data.
    /// Used by Edge to return to the RP.
    pub credential_id: Option<Vec<u8>>,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_2
    /// Since VERSION 2
    pub extensions: Option<Vec<WebAuthnExtensionMakeCredentialOutput>>,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_3
    /// One of the WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
    /// the transport that was used.
    pub used_transport: CtapTransport,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_4
    pub ep_att: bool,
    pub large_blob_supported: bool,
    pub resident_key: bool,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_5
    pub prf_enabled: bool,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_6
    pub unsigned_extension_outputs: Option<Vec<u8>>,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_7
    pub hmac_secret: Option<HmacSecretSalt>,

    /// ThirdPartyPayment Credential or not.
    pub third_party_payment: bool,

    //
    // Following fields have been added in WEBAUTHN_CREDENTIAL_ATTESTATION_VERSION_8
    /// Multiple WEBAUTHN_CTAP_TRANSPORT_* bits will be set corresponding to
    /// the transports that are supported.
    pub transports: Option<Vec<CtapTransport>>,

    /// UTF-8 encoded JSON serialization of the client data.
    pub client_data_json: Option<Vec<u8>>,

    /// UTF-8 encoded JSON serialization of the RegistrationResponse.
    pub registration_response_json: Option<Vec<u8>>,
}

impl PluginMakeCredentialResponse {
    pub fn to_ctap_response(self) -> Result<Vec<u8>, WinWebAuthnError> {
        let attestation = self.try_into()?;
        let mut response_len = 0;
        let mut response_ptr = std::ptr::null_mut();
        // SAFETY: we construct valid input and check the OS error code before using the returned
        // value.
        unsafe {
            webauthn_encode_make_credential_response(
                &attestation,
                &mut response_len,
                &mut response_ptr,
            )?
            .ok()
            .map_err(|err| {
                WinWebAuthnError::with_cause(
                    ErrorKind::WindowsInternal,
                    "WebAuthNEncodeMakeCredentialResponse() failed",
                    err,
                )
            })?;

            if response_ptr.is_null() {
                return Err(WinWebAuthnError::new(
                    ErrorKind::WindowsInternal,
                    "Received null pointer from WebAuthNEncodeMakeCredentialResponse",
                ));
            }
            let response = std::slice::from_raw_parts(response_ptr, response_len as usize).to_vec();
            // Ideally, we wouldn't have Windows allocate this in COM, and then
            // we reallocate locally and then reallocate for COM.
            CoTaskMemFree(Some(response_ptr.cast()));

            Ok(response)
        }
    }
}

impl TryFrom<PluginMakeCredentialResponse> for WEBAUTHN_CREDENTIAL_ATTESTATION {
    type Error = WinWebAuthnError;

    fn try_from(value: PluginMakeCredentialResponse) -> Result<Self, Self::Error> {
        // Convert format type to UTF-16
        let format_type_utf16 = value.format_type.to_utf16();
        let pwszFormatType = format_type_utf16.as_ptr();
        std::mem::forget(format_type_utf16);

        // Get authenticator data pointer and length
        let pbAuthenticatorData = value.authenticator_data.as_ptr();
        let cbAuthenticatorData = value.authenticator_data.len() as u32;
        std::mem::forget(value.authenticator_data);

        // Get optional attestation statement pointer and length
        let (pbAttestation, cbAttestation) = match value.attestation_statement.as_ref() {
            Some(data) => (data.as_ptr(), data.len() as u32),
            None => (std::ptr::null(), 0),
        };
        std::mem::forget(value.attestation_statement);

        // Get optional attestation object pointer and length
        let (pbAttestationObject, cbAttestationObject) = match value.attestation_object.as_ref() {
            Some(data) => (data.as_ptr(), data.len() as u32),
            None => (std::ptr::null(), 0),
        };
        std::mem::forget(value.attestation_object);

        // Get optional credential ID pointer and length
        let (pbCredentialId, cbCredentialId) = match value.credential_id.as_ref() {
            Some(data) => (data.as_ptr(), data.len() as u32),
            None => (std::ptr::null(), 0),
        };
        std::mem::forget(value.credential_id);

        // Convert extensions (TODO: implement proper extension conversion)
        let extensions = WEBAUTHN_EXTENSIONS {
            cExtensions: 0,
            pExtensions: std::ptr::null(),
        };

        // Convert used transport enum to bitmask
        let dwUsedTransport = value.used_transport as u32;

        // Get optional unsigned extension outputs pointer and length
        let (pbUnsignedExtensionOutputs, cbUnsignedExtensionOutputs) =
            match value.unsigned_extension_outputs.as_ref() {
                Some(data) => (data.as_ptr(), data.len() as u32),
                None => (std::ptr::null(), 0),
            };
        std::mem::forget(value.unsigned_extension_outputs);

        // Convert optional HMAC secret (TODO: implement proper conversion)
        let pHmacSecret = std::ptr::null();

        // Convert optional transports to bitmask
        let dwTransports = value
            .transports
            .as_ref()
            .map_or(0, |t| t.iter().map(|transport| *transport as u32).sum());

        // Get optional client data JSON pointer and length
        let (pbClientDataJSON, cbClientDataJSON) = match value.client_data_json.as_ref() {
            Some(data) => (data.as_ptr(), data.len() as u32),
            None => (std::ptr::null(), 0),
        };
        std::mem::forget(value.client_data_json);

        // Get optional registration response JSON pointer and length
        let (pbRegistrationResponseJSON, cbRegistrationResponseJSON) =
            match value.registration_response_json.as_ref() {
                Some(data) => (data.as_ptr(), data.len() as u32),
                None => (std::ptr::null(), 0),
            };
        std::mem::forget(value.registration_response_json);

        let attestation = WEBAUTHN_CREDENTIAL_ATTESTATION {
            // Use version 8 to include all fields
            dwVersion: 8,
            pwszFormatType,
            cbAuthenticatorData,
            pbAuthenticatorData,
            cbAttestation,
            pbAttestation,
            // TODO: Support decode type. Just using WEBAUTHN_ATTESTATION_DECODE_NONE (0) for now.
            dwAttestationDecodeType: 0,
            pvAttestationDecode: std::ptr::null(),
            cbAttestationObject,
            pbAttestationObject,
            cbCredentialId,
            pbCredentialId,
            Extensions: extensions,
            dwUsedTransport,
            bEpAtt: value.ep_att,
            bLargeBlobSupported: value.large_blob_supported,
            bResidentKey: value.resident_key,
            bPrfEnabled: value.prf_enabled,
            cbUnsignedExtensionOutputs,
            pbUnsignedExtensionOutputs,
            pHmacSecret,
            bThirdPartyPayment: value.third_party_payment,
            dwTransports,
            cbClientDataJSON,
            pbClientDataJSON,
            cbRegistrationResponseJSON,
            pbRegistrationResponseJSON,
        };
        Ok(attestation)
    }
}

webauthn_call!("WebAuthNEncodeMakeCredentialResponse" as 
/// Encode a credential attestation response to a COM-allocated byte buffer
/// containing a CTAP CBOR `authenticatorMakeCredential` response structure.
/// 
/// Returns [S_OK](windows::Win32::Foundation::S_OK) on success.
/// 
/// # Arguments
/// - `pCredentialAttestation`: A pointer to [WEBAUTHN_CREDENTIAL_ATTESTATION] to encode.
/// - `pcbResp`: A pointer to a u32, which will be filled with the length of the response buffer.
/// - `ppbResponse`: An indirect pointer to a byte buffer, which will be written to on succces.
fn webauthn_encode_make_credential_response(
    pCredentialAttestation: *const WEBAUTHN_CREDENTIAL_ATTESTATION,
    pcbResp: *mut u32,
    ppbResponse: *mut *mut u8
) -> HRESULT);

// GetAssertion types

pub(super) struct WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY {
    /// Version of this structure, to allow for modifications in the future.
    pub _dwVersion: u32,

    /// Key type
    pub _lKty: i32,

    /// Hash Algorithm: ES256, ES384, ES512
    pub _lAlg: i32,

    /// Curve
    pub _lCrv: i32,

    /// Size of "x" (X Coordinate)
    pub _cbX: u32,

    /// "x" (X Coordinate) data. Big Endian.
    pub _pbX: *const u8,

    /// Size of "y" (Y Coordinate)
    pub _cbY: u32,

    /// "y" (Y Coordinate) data. Big Endian.
    pub _pbY: *const u8,
}

pub(super) struct WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION {
    /// Version of this structure, to allow for modifications in the future.
    pub _dwVersion: u32,

    // Platform's key agreement public key
    pub _pKeyAgreement: *const WEBAUTHN_CTAPCBOR_ECC_PUBLIC_KEY,

    pub _cbEncryptedSalt: u32,
    pub _pbEncryptedSalt: *const u8,

    pub _cbSaltAuth: u32,
    pub _pbSaltAuth: *const u8,
}

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub(super) struct WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
    pub dwVersion: u32,
    pub pwszRpId: *const u16, // PCWSTR
    pub cbRpId: u32,
    pub pbRpId: *const u8,
    pub cbClientDataHash: u32,
    pub pbClientDataHash: *const u8,
    pub CredentialList: WEBAUTHN_CREDENTIAL_LIST,
    pub cbCborExtensionsMap: u32,
    pub pbCborExtensionsMap: *const u8,
    pub pAuthenticatorOptions: *const WebAuthnCtapCborAuthenticatorOptions,

    // Pin Auth (Optional)
    /// Zero length PinAuth is included in the request
    pub fEmptyPinAuth: BOOL,
    pub cbPinAuth: u32,
    pub pbPinAuth: *const u8,

    // HMAC Salt Extension (Optional)
    pub pHmacSaltExtension: *const WEBAUTHN_CTAPCBOR_HMAC_SALT_EXTENSION,

    // PRF Extension
    pub cbHmacSecretSaltValues: u32,
    pub pbHmacSecretSaltValues: *const u8,

    pub dwPinProtocol: u32,

    //"credBlob": true  extension
    pub lCredBlobExt: i32,

    //"largeBlobKey": true extension
    pub lLargeBlobKeyExt: i32,

    //"largeBlob" extension
    pub dwCredLargeBlobOperation: u32,
    pub cbCredLargeBlobCompressed: u32,
    pub pbCredLargeBlobCompressed: *const u8,
    pub dwCredLargeBlobOriginalSize: u32,

    // "json" extension. Nonzero if present
    pub cbJsonExt: u32,
    pub pbJsonExt: *const u8,
}

#[derive(Debug)]
pub struct PluginGetAssertionRequest {
    inner: *const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
    pub window_handle: HWND,
    pub transaction_id: GUID,
    pub request_signature: Vec<u8>,
    pub request_hash: Vec<u8>,
}

impl PluginGetAssertionRequest {
    pub fn rp_id(&self) -> &str {
        unsafe {
            let request = &*self.inner;
            let slice = std::slice::from_raw_parts(request.pbRpId, request.cbRpId as usize);
            str::from_utf8_unchecked(slice)
        }
    }

    pub fn client_data_hash(&self) -> &[u8] {
        let inner = self.as_ref();
        // SAFETY: Verified by Windows
        unsafe {
            std::slice::from_raw_parts(inner.pbClientDataHash, inner.cbClientDataHash as usize)
        }
    }

    pub fn allow_credentials(&self) -> impl Iterator<Item = CredentialEx<'_>> {
        // SAFETY: When this is constructed from Self::try_from_ptr(), the Windows decode API
        // constructs valid pointers.
        unsafe { self.as_ref().CredentialList.iter() }
    }

    // TODO: Support extensions
    // pub fn extensions(&self) -> Options<Extensions> {}

    pub fn authenticator_options(&self) -> Option<WebAuthnCtapCborAuthenticatorOptions> {
        let ptr = self.as_ref().pAuthenticatorOptions;
        if ptr.is_null() {
            return None;
        }
        unsafe { Some(*ptr) }
    }

    /// # Safety
    /// When calling this method, callers must ensure:
    /// - `ptr` must be convertible to a reference.
    /// - pbEncodedRequest must be non-null and have the length specified in cbEncodedRequest.
    /// - pbEncodedRequest must point to a valid byte string of a CTAP GetAssertion request.
    pub(super) unsafe fn try_from_ptr(
        value: NonNull<WEBAUTHN_PLUGIN_OPERATION_REQUEST>,
    ) -> Result<PluginGetAssertionRequest, WinWebAuthnError> {
        // SAFETY: caller must ensure that ptr is convertible to a reference.
        let request = value.as_ref();
        if !matches!(request.requestType, WebAuthnPluginRequestType::CTAP2_CBOR) {
            return Err(WinWebAuthnError::new(
                ErrorKind::Serialization,
                "Unknown plugin operation request type",
            ));
        }
        // SAFETY: Caller must ensure that the pointer and count is valid.
        let request_slice =
            std::slice::from_raw_parts(request.pbEncodedRequest, request.cbEncodedRequest as usize);
        let request_hash = crypto::hash_sha256(request_slice).map_err(|err| {
            WinWebAuthnError::with_cause(ErrorKind::WindowsInternal, "failed to hash request", err)
        })?;
        let mut assertion_request: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST =
            std::ptr::null_mut();
        webauthn_decode_get_assertion_request(
            request.cbEncodedRequest,
            request.pbEncodedRequest,
            &mut assertion_request,
        )?
        .ok()
        .map_err(|err| {
            WinWebAuthnError::with_cause(
                ErrorKind::WindowsInternal,
                "Failed to decode get assertion request",
                err,
            )
        })?;

        if request.hWnd.is_invalid() {
            return Err(WinWebAuthnError::new(
                ErrorKind::WindowsInternal,
                "Invalid handle received",
            ));
        }

        Ok(Self {
            // SAFETY: Windows should return a valid decoded assertion request struct.
            inner: assertion_request as *const WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
            window_handle: request.hWnd,
            transaction_id: request.transactionId,
            // SAFETY: Caller is expected to ensure that signature buffer parameters are correct.
            request_signature: std::slice::from_raw_parts(
                request.pbRequestSignature,
                request.cbRequestSignature as usize,
            )
            .to_vec(),
            request_hash,
        })
    }
}

impl AsRef<WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST> for PluginGetAssertionRequest {
    fn as_ref(&self) -> &WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST {
        unsafe { &*self.inner }
    }
}

impl Drop for PluginGetAssertionRequest {
    fn drop(&mut self) {
        if !self.inner.is_null() {
            // SAFETY: the caller is responsible for ensuring that this pointer
            // is allocated with an allocator corresponding to this free
            // function.
            unsafe {
                // leak memory if we cannot find the free function
                _ = webauthn_free_decoded_get_assertion_request(
                    self.inner as *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST,
                );
            }
        }
    }
}

// Windows API function signatures for decoding get assertion requests
webauthn_call!("WebAuthNDecodeGetAssertionRequest" as
/// Decodes a CTAP GetAssertion request.
///
/// On success, a [WEBAUTHN_CTAPCBOR_MAKE_CREDENTIAL_REQUEST] will be written to
/// `ppGetAssertionRequest`, which must be freed by a call to
/// [webauthn_free_decoded_get_assertion_request].
/// 
/// # Arguments
/// - `pbEncoded`: a COM-allocated buffer pointing to a CTAP CBOR get assertion request.
/// - `ppGetAssertionRequest`: An indirect pointer to a [WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST].
/// 
/// # Safety
/// - `pbEncoded` must have been allocated by Windows COM.
/// - `pbEncoded` must be non-null and have the length specified in cbEncoded.
fn webauthn_decode_get_assertion_request(
    cbEncoded: u32,
    pbEncoded: *const u8,
    ppGetAssertionRequest: *mut *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST
) -> HRESULT);

webauthn_call!("WebAuthNFreeDecodedGetAssertionRequest" as
/// Frees a decoded get assertion request from [webauthn_free_decoded_get_assertion_request].
/// 
/// # Arguments
/// - `pGetAssertionRequest`: An pointer to a [WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST] to be freed.
fn webauthn_free_decoded_get_assertion_request(
    pGetAssertionRequest: *mut WEBAUTHN_CTAPCBOR_GET_ASSERTION_REQUEST
) -> ());

// CancelOperation Types
pub(super) struct WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST {
    transactionId: GUID,
    cbRequestSignature: u32,
    pbRequestSignature: *const u8,
}

pub struct PluginCancelOperationRequest {
    inner: NonNull<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST>,
}

impl PluginCancelOperationRequest {
    /// Request transaction ID
    pub fn transaction_id(&self) -> GUID {
        self.as_ref().transactionId
    }

    /// Request signature.
    pub fn request_signature(&self) -> &[u8] {
        unsafe {
            std::slice::from_raw_parts(
                self.as_ref().pbRequestSignature,
                self.as_ref().cbRequestSignature as usize,
            )
        }
    }
}

impl AsRef<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST> for PluginCancelOperationRequest {
    fn as_ref(&self) -> &WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST {
        // SAFETY: Pointer is received from Windows so we assume it is correct.
        unsafe { self.inner.as_ref() }
    }
}

#[doc(hidden)]
impl From<NonNull<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST>> for PluginCancelOperationRequest {
    fn from(value: NonNull<WEBAUTHN_PLUGIN_CANCEL_OPERATION_REQUEST>) -> Self {
        Self { inner: value }
    }
}

/// Plugin lock status enum as defined in the IDL
#[repr(u32)]
#[derive(Debug, Copy, Clone)]
pub enum PluginLockStatus {
    PluginLocked = 0,
    PluginUnlocked = 1,
}
