//! `LAPublicKey` wrappers.

use crate::ffi;
use crate::la_error::Result;
use crate::private::{bridge_bool, bridge_bytes, bridge_unit, cstring, OwnedHandle};

/// String-backed `SecKeyAlgorithm` wrapper used by the `LocalAuthentication` key APIs.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SecKeyAlgorithm(String);

impl SecKeyAlgorithm {
    /// Create an algorithm from the raw `SecKeyAlgorithm` name.
    #[must_use]
    pub fn from_raw_name(name: impl Into<String>) -> Self {
        Self(name.into())
    }

    /// Borrow the raw `SecKeyAlgorithm` name.
    #[must_use]
    pub fn raw_name(&self) -> &str {
        &self.0
    }

    /// `SecKeyAlgorithm.ecdsaSignatureMessageX962SHA256`.
    #[must_use]
    pub fn ecdsa_signature_message_x962_sha256() -> Self {
        Self::from_raw_name("algid:sign:ECDSA:message-X962:SHA256")
    }

    /// `SecKeyAlgorithm.ecdsaSignatureDigestX962SHA256`.
    #[must_use]
    pub fn ecdsa_signature_digest_x962_sha256() -> Self {
        Self::from_raw_name("algid:sign:ECDSA:digest-X962:SHA256")
    }

    /// `SecKeyAlgorithm.eciesEncryptionStandardVariableIVX963SHA256AESGCM`.
    #[must_use]
    pub fn ecies_encryption_standard_variable_iv_x963_sha256_aes_gcm() -> Self {
        Self::from_raw_name("algid:encrypt:ECIES:ECDH:KDFX963:SHA256:AESGCM-KDFIV")
    }

    /// `SecKeyAlgorithm.eciesEncryptionCofactorVariableIVX963SHA256AESGCM`.
    #[must_use]
    pub fn ecies_encryption_cofactor_variable_iv_x963_sha256_aes_gcm() -> Self {
        Self::from_raw_name("algid:encrypt:ECIES:ECDHC:KDFX963:SHA256:AESGCM-KDFIV")
    }

    /// `SecKeyAlgorithm.ecdhKeyExchangeCofactorX963SHA256`.
    #[must_use]
    pub fn ecdh_key_exchange_cofactor_x963_sha256() -> Self {
        Self::from_raw_name("algid:keyexchange:ECDHC:KDFX963:SHA256")
    }
}

impl From<&str> for SecKeyAlgorithm {
    fn from(value: &str) -> Self {
        Self::from_raw_name(value)
    }
}

impl From<String> for SecKeyAlgorithm {
    fn from(value: String) -> Self {
        Self::from_raw_name(value)
    }
}

/// Typed wrapper for the `SecKeyKeyExchangeParameter*` dictionary accepted by `LAPrivateKey::exchange_keys_with_public_key`.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SecKeyExchangeParameters {
    requested_size: Option<usize>,
    shared_info: Option<Vec<u8>>,
}

impl SecKeyExchangeParameters {
    /// Create an empty parameter dictionary.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Create a dictionary that requests a derived key of the supplied length.
    #[must_use]
    pub const fn with_requested_size(requested_size: usize) -> Self {
        Self {
            requested_size: Some(requested_size),
            shared_info: None,
        }
    }

    /// Override the requested derived-key length.
    #[must_use]
    pub const fn requested_size(mut self, requested_size: usize) -> Self {
        self.requested_size = Some(requested_size);
        self
    }

    /// Attach additional shared info for algorithms that support KDF context data.
    #[must_use]
    pub fn with_shared_info(mut self, shared_info: impl Into<Vec<u8>>) -> Self {
        self.shared_info = Some(shared_info.into());
        self
    }

    /// Clear any previously configured shared info.
    #[must_use]
    pub fn without_shared_info(mut self) -> Self {
        self.shared_info = None;
        self
    }

    #[must_use]
    pub(crate) const fn requested_size_value(&self) -> Option<usize> {
        self.requested_size
    }

    #[must_use]
    pub(crate) fn shared_info_value(&self) -> Option<&[u8]> {
        self.shared_info.as_deref()
    }
}

/// Managed wrapper around Apple's `LAPublicKey`.
#[derive(Debug)]
pub struct LAPublicKey {
    handle: OwnedHandle,
}

impl LAPublicKey {
    pub(crate) fn from_raw(raw: std::ptr::NonNull<core::ffi::c_void>) -> Self {
        Self {
            handle: OwnedHandle::new(raw, ffi::la_public_key::la_public_key_release),
        }
    }

    /// Export the public-key bytes.
    ///
    /// # Errors
    ///
    /// Returns a mapped framework or bridge error if export fails.
    pub fn export_bytes(&self) -> Result<Vec<u8>> {
        bridge_bytes(|out, out_len, error_out| unsafe {
            ffi::la_public_key::la_public_key_export_bytes(
                self.handle.as_ptr(),
                out,
                out_len,
                error_out,
            )
        })
    }

    /// Check whether an algorithm can encrypt with this key.
    ///
    /// # Errors
    ///
    /// Returns an error if the Swift bridge rejects the request.
    pub fn can_encrypt_using(&self, algorithm: &SecKeyAlgorithm) -> Result<bool> {
        let algorithm = cstring(algorithm.raw_name())?;
        bridge_bool(|out, error_out| unsafe {
            ffi::la_public_key::la_public_key_can_encrypt_using_algorithm(
                self.handle.as_ptr(),
                algorithm.as_ptr(),
                out,
                error_out,
            )
        })
    }

    /// Encrypt data with this key.
    ///
    /// # Errors
    ///
    /// Returns a mapped framework or bridge error if encryption fails.
    pub fn encrypt(&self, data: &[u8], algorithm: &SecKeyAlgorithm) -> Result<Vec<u8>> {
        let algorithm = cstring(algorithm.raw_name())?;
        bridge_bytes(|out, out_len, error_out| unsafe {
            ffi::la_public_key::la_public_key_encrypt_data(
                self.handle.as_ptr(),
                data.as_ptr(),
                data.len(),
                algorithm.as_ptr(),
                out,
                out_len,
                error_out,
            )
        })
    }

    /// Check whether an algorithm can verify signatures with this key.
    ///
    /// # Errors
    ///
    /// Returns an error if the Swift bridge rejects the request.
    pub fn can_verify_using(&self, algorithm: &SecKeyAlgorithm) -> Result<bool> {
        let algorithm = cstring(algorithm.raw_name())?;
        bridge_bool(|out, error_out| unsafe {
            ffi::la_public_key::la_public_key_can_verify_using_algorithm(
                self.handle.as_ptr(),
                algorithm.as_ptr(),
                out,
                error_out,
            )
        })
    }

    /// Verify a signature with this key.
    ///
    /// # Errors
    ///
    /// Returns a mapped framework or bridge error if verification fails.
    pub fn verify(
        &self,
        signed_data: &[u8],
        signature: &[u8],
        algorithm: &SecKeyAlgorithm,
    ) -> Result<()> {
        let algorithm = cstring(algorithm.raw_name())?;
        bridge_unit(|error_out| unsafe {
            ffi::la_public_key::la_public_key_verify_data(
                self.handle.as_ptr(),
                signed_data.as_ptr(),
                signed_data.len(),
                signature.as_ptr(),
                signature.len(),
                algorithm.as_ptr(),
                error_out,
            )
        })
    }
}
