//! `SecretStorageRepository` trait + Linux Secret Service adapter.
//!
//! Maps to SDD-078 (`SecretStorageRepository shall persist secrets only
//! through platform secure storage service interfaces`) and ADR-006
//! (`SecureStore trait and per-platform adapters`).

use std::fmt;

use zeroize::Zeroize;

/// Trait every platform adapter implements.
///
/// The API is intentionally narrow: a secret is identified by a stable
/// `name` (e.g. `"identity.primary"`, `"server.<bookmark-id>.password"`)
/// and treated as an opaque UTF-8 byte sequence. The trait does **not**
/// expose handles, paths, or backend-specific types — the
/// `chanora_storage` crate must not leak those upward (SAD-067).
pub trait SecretStorageRepository: Send + Sync {
    /// Store (or replace) a secret under `name`. The implementation
    /// must not write `secret` to plaintext disk, logs, or diagnostic
    /// exports (SS-AUD-001..004).
    fn set(&self, name: &str, secret: &Secret) -> Result<(), SecureStoreError>;

    /// Retrieve a secret previously stored under `name`.
    ///
    /// Returns `Err(SecureStoreError::NotFound)` if no entry exists.
    fn get(&self, name: &str) -> Result<Secret, SecureStoreError>;

    /// Remove a secret. Returns `Err(SecureStoreError::NotFound)` if
    /// it was already absent. Idempotent variants are intentionally
    /// not provided here; callers must handle `NotFound`.
    fn delete(&self, name: &str) -> Result<(), SecureStoreError>;
}

/// Typed error DTO. Production code (`chanora_bridge`) will widen this
/// when more failure modes are observed; the PoC only needs to prove
/// SS-AUD-005 (safe error mapping).
#[derive(Debug, thiserror::Error)]
pub enum SecureStoreError {
    #[error("secret not found")]
    NotFound,
    #[error("platform secure storage unavailable: {0}")]
    Unavailable(String),
    #[error("platform secure storage backend rejected the operation: {0}")]
    Backend(String),
}

/// Owned, zeroed-on-drop secret material.
///
/// `Debug` and `Display` deliberately do **not** print the inner bytes
/// — this is part of the SS-AUD-003 (no-secrets-in-logs) defense in
/// depth. Tests in this crate rely on this behavior.
#[derive(Clone, Zeroize)]
#[zeroize(drop)]
pub struct Secret(Vec<u8>);

impl Secret {
    pub fn from_utf8(s: impl Into<String>) -> Self {
        Self(s.into().into_bytes())
    }

    pub fn from_bytes(b: impl Into<Vec<u8>>) -> Self {
        Self(b.into())
    }

    pub fn as_bytes(&self) -> &[u8] {
        &self.0
    }

    pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
        std::str::from_utf8(&self.0)
    }

    pub fn len(&self) -> usize {
        self.0.len()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl fmt::Debug for Secret {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Secret(<{} bytes redacted>)", self.0.len())
    }
}

impl fmt::Display for Secret {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // Same redaction shape as Debug.
        write!(f, "<redacted>")
    }
}

impl PartialEq for Secret {
    fn eq(&self, other: &Self) -> bool {
        // Constant-time-ish compare via fixed length-first then byte compare.
        // The PoC does not promise true constant-time semantics.
        self.0 == other.0
    }
}

// ---------- Linux adapter ----------

#[cfg(target_os = "linux")]
pub mod linux {
    use super::*;
    use keyring::Entry;

    /// Which Linux backend to use.
    ///
    /// Both are acceptable under SysRS-053 / SysRS-162 ("Secret Service,
    /// libsecret, or equivalent"):
    ///
    /// * `SecretService`: D-Bus Secret Service (gnome-keyring, kwallet,
    ///   KeePassXC, etc.). Preferred on interactive desktop sessions.
    /// * `Keyutils`: kernel session keyring (`add_key(2)` /
    ///   `request_key(2)`). Always available on Linux, no D-Bus
    ///   dependency, but secrets live only for the session lifetime.
    #[derive(Clone, Copy, Debug, PartialEq, Eq)]
    pub enum LinuxBackend {
        /// Default user keyring backend selected by the `keyring` crate
        /// at compile time. With the features this PoC enables, this
        /// is Secret Service.
        Default,
        /// Force the kernel keyutils backend. Useful for headless
        /// environments and for production code paths where no
        /// graphical session is available.
        Keyutils,
    }

    /// Linux adapter using the `keyring` crate. The choice of backend
    /// is made at construction so the rest of the application can
    /// remain backend-agnostic.
    pub struct LinuxSecureStore {
        service: String,
        backend: LinuxBackend,
    }

    impl LinuxSecureStore {
        /// Construct an adapter backed by the default `keyring`
        /// credential store (Secret Service with the features compiled
        /// in this PoC).
        pub fn new(service: impl Into<String>) -> Self {
            Self { service: service.into(), backend: LinuxBackend::Default }
        }

        /// Construct an adapter backed by the kernel keyutils session
        /// keyring. Production code may select this when no D-Bus
        /// session is available.
        pub fn new_keyutils(service: impl Into<String>) -> Self {
            Self { service: service.into(), backend: LinuxBackend::Keyutils }
        }

        fn entry(&self, name: &str) -> Result<Entry, SecureStoreError> {
            let result = match self.backend {
                LinuxBackend::Default => Entry::new(&self.service, name),
                LinuxBackend::Keyutils => {
                    let cred = keyring::keyutils::KeyutilsCredential::new_with_target(
                        None,
                        &self.service,
                        name,
                    )
                    .map_err(|e| SecureStoreError::Unavailable(format!("{e}")))?;
                    Ok(Entry::new_with_credential(Box::new(cred)))
                }
            };
            result.map_err(|e| SecureStoreError::Unavailable(format!("{e}")))
        }
    }

    impl SecretStorageRepository for LinuxSecureStore {
        fn set(&self, name: &str, secret: &Secret) -> Result<(), SecureStoreError> {
            let entry = self.entry(name)?;
            let s = secret.as_str().map_err(|e| {
                SecureStoreError::Backend(format!("secret is not valid utf-8: {e}"))
            })?;
            entry
                .set_password(s)
                .map_err(|e| SecureStoreError::Backend(format!("{e}")))
        }

        fn get(&self, name: &str) -> Result<Secret, SecureStoreError> {
            let entry = self.entry(name)?;
            match entry.get_password() {
                Ok(s) => Ok(Secret::from_utf8(s)),
                Err(keyring::Error::NoEntry) => Err(SecureStoreError::NotFound),
                Err(e) => Err(SecureStoreError::Backend(format!("{e}"))),
            }
        }

        fn delete(&self, name: &str) -> Result<(), SecureStoreError> {
            let entry = self.entry(name)?;
            match entry.delete_credential() {
                Ok(()) => Ok(()),
                Err(keyring::Error::NoEntry) => Err(SecureStoreError::NotFound),
                Err(e) => Err(SecureStoreError::Backend(format!("{e}"))),
            }
        }
    }

    /// Back-compat type alias — older code in this PoC referred to
    /// `SecretServiceAdapter` before the backend choice existed.
    pub type SecretServiceAdapter = LinuxSecureStore;
}
