//! Safe adapters around native credential-store crates.
//!
//! This is the only module that selects operating-system implementations.
//! IronStorage performs no direct FFI and contains no unsafe code: the Apple,
//! Windows, and Linux crates own their respective Security Framework,
//! Credential Manager, and Secret Service boundaries. Operations are serialized
//! by the parent `SecretStore`, as required by the Windows adapter contract.

use std::sync::Arc;

#[cfg(any(target_os = "ios", target_os = "macos"))]
use std::collections::HashMap;

#[cfg(any(target_os = "ios", target_os = "macos"))]
use security_framework::{
    access_control::{ProtectionMode, SecAccessControl},
    passwords::{
        AccessControlOptions, PasswordOptions, delete_generic_password_options, generic_password,
        set_generic_password_options,
    },
};

use keyring_core::{CredentialStore, Entry};

use super::{SecretLocator, SecretProtection, SecretStoreBackend, SecretStoreError};
use crate::repository::SecretBytes;

pub struct NativeSecretBackend {
    #[cfg(any(target_os = "linux", target_os = "windows"))]
    store: Arc<CredentialStore>,
    #[cfg(target_os = "ios")]
    protected: Arc<CredentialStore>,
    #[cfg(target_os = "macos")]
    keychain: Arc<CredentialStore>,
    #[cfg(target_os = "macos")]
    protected: Arc<CredentialStore>,
}

impl NativeSecretBackend {
    pub fn new() -> Result<Self, SecretStoreError> {
        #[cfg(target_os = "linux")]
        {
            let store: Arc<CredentialStore> =
                zbus_secret_service_keyring_store::Store::new().map_err(map_error)?;
            Ok(Self { store })
        }
        #[cfg(target_os = "windows")]
        {
            let store: Arc<CredentialStore> =
                windows_native_keyring_store::Store::new().map_err(map_error)?;
            Ok(Self { store })
        }
        #[cfg(target_os = "ios")]
        {
            let protected: Arc<CredentialStore> =
                apple_native_keyring_store::protected::Store::new().map_err(map_error)?;
            Ok(Self { protected })
        }
        #[cfg(target_os = "macos")]
        {
            let keychain: Arc<CredentialStore> =
                apple_native_keyring_store::keychain::Store::new().map_err(map_error)?;
            let protected: Arc<CredentialStore> =
                apple_native_keyring_store::protected::Store::new().map_err(map_error)?;
            Ok(Self {
                keychain,
                protected,
            })
        }
        #[cfg(not(any(
            target_os = "ios",
            target_os = "linux",
            target_os = "macos",
            target_os = "windows"
        )))]
        {
            Err(SecretStoreError::Unavailable)
        }
    }

    fn entry(
        &self,
        locator: &SecretLocator,
        protection: SecretProtection,
    ) -> Result<Entry, SecretStoreError> {
        let (service, user) = locator.service_and_user();
        #[cfg(any(target_os = "linux", target_os = "windows"))]
        {
            if protection != SecretProtection::DeviceUnlocked {
                return Err(SecretStoreError::UnsupportedProtection);
            }
            self.store.build(service, &user, None).map_err(map_error)
        }
        #[cfg(target_os = "ios")]
        {
            let modifiers = presence_modifiers(protection);
            self.protected
                .build(service, &user, modifiers.as_ref())
                .map_err(map_error)
        }
        #[cfg(target_os = "macos")]
        {
            match protection {
                SecretProtection::DeviceUnlocked => {
                    self.keychain.build(service, &user, None).map_err(map_error)
                }
                SecretProtection::RequireUserPresence => {
                    let modifiers = presence_modifiers(protection);
                    self.protected
                        .build(service, &user, modifiers.as_ref())
                        .map_err(map_error)
                }
                SecretProtection::BiometryCurrentSet => Err(SecretStoreError::Unavailable),
            }
        }
        #[cfg(not(any(
            target_os = "ios",
            target_os = "linux",
            target_os = "macos",
            target_os = "windows"
        )))]
        {
            let _ = (service, user, protection);
            Err(SecretStoreError::Unavailable)
        }
    }
}

impl SecretStoreBackend for NativeSecretBackend {
    fn create(
        &self,
        locator: &SecretLocator,
        protection: SecretProtection,
        value: &[u8],
    ) -> Result<(), SecretStoreError> {
        #[cfg(any(target_os = "ios", target_os = "macos"))]
        if protection == SecretProtection::BiometryCurrentSet {
            return strict_set(locator, value);
        }
        let entry = self.entry(locator, protection)?;
        match entry.get_secret() {
            Ok(existing) => {
                drop(SecretBytes::new(existing));
                Err(SecretStoreError::AlreadyExists)
            }
            Err(keyring_core::Error::NoEntry) => entry.set_secret(value).map_err(map_error),
            Err(error) => Err(map_error(error)),
        }
    }

    fn retrieve(
        &self,
        locator: &SecretLocator,
        protection: SecretProtection,
    ) -> Result<SecretBytes, SecretStoreError> {
        #[cfg(any(target_os = "ios", target_os = "macos"))]
        if protection == SecretProtection::BiometryCurrentSet {
            return generic_password(strict_query(locator))
                .map(SecretBytes::new)
                .map_err(map_security_error);
        }
        self.entry(locator, protection)?
            .get_secret()
            .map(SecretBytes::new)
            .map_err(map_error)
    }

    fn replace(
        &self,
        locator: &SecretLocator,
        protection: SecretProtection,
        value: &[u8],
    ) -> Result<(), SecretStoreError> {
        #[cfg(any(target_os = "ios", target_os = "macos"))]
        if protection == SecretProtection::BiometryCurrentSet {
            delete_generic_password_options(strict_query(locator)).map_err(map_security_error)?;
            return strict_set(locator, value);
        }
        let entry = self.entry(locator, protection)?;
        let existing = entry.get_secret().map_err(map_error)?;
        drop(SecretBytes::new(existing));
        entry.set_secret(value).map_err(map_error)
    }

    fn delete(
        &self,
        locator: &SecretLocator,
        protection: SecretProtection,
    ) -> Result<(), SecretStoreError> {
        #[cfg(any(target_os = "ios", target_os = "macos"))]
        if protection == SecretProtection::BiometryCurrentSet {
            return delete_generic_password_options(strict_query(locator))
                .map_err(map_security_error);
        }
        self.entry(locator, protection)?
            .delete_credential()
            .map_err(map_error)
    }
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
fn presence_modifiers(protection: SecretProtection) -> Option<HashMap<&'static str, &'static str>> {
    match protection {
        SecretProtection::DeviceUnlocked => None,
        SecretProtection::RequireUserPresence => {
            Some(HashMap::from([("access-policy", "require-user-presence")]))
        }
        SecretProtection::BiometryCurrentSet => None,
    }
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
fn strict_query(locator: &SecretLocator) -> PasswordOptions {
    let (service, account) = locator.service_and_user();
    let mut options = PasswordOptions::new_generic_password(service, &account);
    options.use_protected_keychain();
    options
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
fn strict_create_options(locator: &SecretLocator) -> Result<PasswordOptions, SecretStoreError> {
    let access = SecAccessControl::create_with_protection(
        Some(ProtectionMode::AccessibleWhenPasscodeSetThisDeviceOnly),
        AccessControlOptions::BIOMETRY_CURRENT_SET.bits(),
    )
    .map_err(map_security_error)?;
    let mut options = strict_query(locator);
    options.set_access_control(access);
    Ok(options)
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
fn strict_set(locator: &SecretLocator, value: &[u8]) -> Result<(), SecretStoreError> {
    set_generic_password_options(value, strict_create_options(locator)?).map_err(map_security_error)
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
fn map_security_error(error: security_framework::base::Error) -> SecretStoreError {
    match error.code() {
        -25300 => SecretStoreError::Missing,
        -128 => SecretStoreError::Cancelled,
        -25293 | -25308 => SecretStoreError::Denied,
        -50 => SecretStoreError::UnsupportedProtection,
        _ => SecretStoreError::Unavailable,
    }
}

fn map_error(error: keyring_core::Error) -> SecretStoreError {
    match error {
        keyring_core::Error::NoEntry => SecretStoreError::Missing,
        keyring_core::Error::NoStorageAccess(error) => platform_access_error(&error),
        keyring_core::Error::BadEncoding(bytes) => {
            drop(SecretBytes::new(bytes));
            SecretStoreError::Corrupted
        }
        keyring_core::Error::BadDataFormat(bytes, _) => {
            drop(SecretBytes::new(bytes));
            SecretStoreError::Corrupted
        }
        keyring_core::Error::BadStoreFormat(_) | keyring_core::Error::Ambiguous(_) => {
            SecretStoreError::Corrupted
        }
        keyring_core::Error::TooLong(_, _) | keyring_core::Error::Invalid(_, _) => {
            SecretStoreError::InvalidReference
        }
        keyring_core::Error::NotSupportedByStore(_) | keyring_core::Error::NoDefaultStore => {
            SecretStoreError::Unavailable
        }
        keyring_core::Error::PlatformFailure(error) => {
            if platform_cancelled(&error) {
                SecretStoreError::Cancelled
            } else {
                SecretStoreError::Unavailable
            }
        }
        _ => SecretStoreError::Unavailable,
    }
}

#[cfg(any(target_os = "ios", target_os = "macos"))]
fn platform_cancelled(error: &keyring_core::error::PlatformError) -> bool {
    error
        .downcast_ref::<security_framework::base::Error>()
        .is_some_and(|error| error.code() == -128)
}

#[cfg(not(any(target_os = "ios", target_os = "macos")))]
fn platform_cancelled(_error: &keyring_core::error::PlatformError) -> bool {
    false
}

#[cfg(target_os = "linux")]
fn platform_access_error(error: &keyring_core::error::PlatformError) -> SecretStoreError {
    match error.downcast_ref::<secret_service::Error>() {
        Some(secret_service::Error::Prompt) => SecretStoreError::Cancelled,
        _ => SecretStoreError::Denied,
    }
}

#[cfg(not(target_os = "linux"))]
fn platform_access_error(_error: &keyring_core::error::PlatformError) -> SecretStoreError {
    SecretStoreError::Denied
}
