//! Unified signing trait for host-sdk.
//!
//! `HostSigner` abstracts over different signing backends (in-process wallet,
//! hardware wallet, remote signing service) so consumers like `host-substrate`,
//! `host-sso`, and `host-chat-v1` do not depend on `WalletManager` directly.
//!
//! Private key material never crosses crate boundaries — only public keys and
//! signatures are returned.

use std::sync::{Arc, Mutex};

/// Error returned by [`HostSigner`] operations.
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum SignerError {
    /// The wallet is locked and no key material is available.
    #[error("wallet is locked")]
    Locked,
    /// The payload exceeds the allowed size limit.
    #[error("payload too large: {size} bytes (max {max})")]
    PayloadTooLarge { size: usize, max: usize },
    /// The signing operation failed for a backend-specific reason.
    #[error("signing failed: {0}")]
    Backend(String),
}

/// A signing identity that can produce sr25519 signatures without exposing
/// private key material.
///
/// Implementations may be backed by an in-process `WalletManager`, a hardware
/// wallet over HID/BLE, or a remote signing service.
///
/// # Thread safety
///
/// The `Send + Sync` bounds allow `HostSigner` objects to be shared across
/// threads via `Arc<dyn HostSigner>`.
///
/// # Atomicity
///
/// Each method call independently acquires and releases the underlying lock.
/// A `public_key()` call followed by `sign()` is **not** atomic — the wallet
/// could be locked by another thread between the two calls.
pub trait HostSigner: Send + Sync {
    /// The 32-byte sr25519 public key for this signer.
    ///
    /// Returns `Err(SignerError::Locked)` if key material is not available.
    fn public_key(&self) -> Result<[u8; 32], SignerError>;

    /// Sign `payload` with this signer's private key.
    ///
    /// Returns a 64-byte sr25519 signature on success.
    fn sign(&self, payload: &[u8]) -> Result<[u8; 64], SignerError>;
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Map a [`WalletError`](crate::WalletError) to a [`SignerError`].
///
/// Converts typed wallet errors into the signer abstraction's error type,
/// keeping key-material details out of the public `SignerError` surface.
fn map_wallet_err(e: crate::WalletError, _payload_len: usize) -> SignerError {
    use crate::WalletError;
    match e {
        WalletError::Locked => SignerError::Locked,
        WalletError::PayloadTooLarge { size, max } => SignerError::PayloadTooLarge { size, max },
        other => {
            // Log the full error for debugging, but expose only a generic
            // message across FFI to prevent leaking internal details (file
            // paths, keychain error codes, memory addresses).
            log::error!("wallet backend error: {other}");
            SignerError::Backend("signing operation failed".to_string())
        }
    }
}

// ---------------------------------------------------------------------------
// Concrete implementations backed by WalletManager
// ---------------------------------------------------------------------------

/// Signs with the root sr25519 keypair (no derivation path).
pub struct RootSigner {
    wallet: Arc<Mutex<crate::WalletManager>>,
}

impl std::fmt::Debug for RootSigner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("RootSigner").finish_non_exhaustive()
    }
}

impl RootSigner {
    /// Create a `RootSigner` from a shared wallet.
    pub fn new(wallet: Arc<Mutex<crate::WalletManager>>) -> Self {
        Self { wallet }
    }
}

impl HostSigner for RootSigner {
    fn public_key(&self) -> Result<[u8; 32], SignerError> {
        self.wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .root_public_key()
            .ok_or(SignerError::Locked)
    }

    fn sign(&self, payload: &[u8]) -> Result<[u8; 64], SignerError> {
        let sig = self
            .wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .sign_root(payload)
            .map_err(|e| map_wallet_err(e, payload.len()))?;
        let mut arr = [0u8; 64];
        arr.copy_from_slice(&sig);
        Ok(arr)
    }
}

/// Signs with a per-app derived sr25519 keypair (`//app//<app_id>//<index>`).
pub struct AppSigner {
    wallet: Arc<Mutex<crate::WalletManager>>,
    app_id: String,
    index: u32,
}

impl std::fmt::Debug for AppSigner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("AppSigner")
            .field("app_id", &self.app_id)
            .field("index", &self.index)
            .finish_non_exhaustive()
    }
}

impl AppSigner {
    /// Create an `AppSigner` for a specific app derivation path.
    pub fn new(wallet: Arc<Mutex<crate::WalletManager>>, app_id: String, index: u32) -> Self {
        Self {
            wallet,
            app_id,
            index,
        }
    }
}

impl HostSigner for AppSigner {
    fn public_key(&self) -> Result<[u8; 32], SignerError> {
        self.wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .app_public_key(&self.app_id, self.index)
            .map_err(|e| map_wallet_err(e, 0))
    }

    fn sign(&self, payload: &[u8]) -> Result<[u8; 64], SignerError> {
        let sig = self
            .wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .sign(&self.app_id, self.index, payload)
            .map_err(|e| map_wallet_err(e, payload.len()))?;
        let mut arr = [0u8; 64];
        arr.copy_from_slice(&sig);
        Ok(arr)
    }
}

/// Signs with the `//wallet` keypair used for chat and SSO identity.
///
/// This replaces both the `chat_sign_key()` raw-bytes export and the
/// `SsoSigner` trait, keeping all key material inside `host-wallet`.
pub struct WalletPathSigner {
    wallet: Arc<Mutex<crate::WalletManager>>,
}

impl std::fmt::Debug for WalletPathSigner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("WalletPathSigner").finish_non_exhaustive()
    }
}

impl WalletPathSigner {
    /// Create a `WalletPathSigner` from a shared wallet.
    pub fn new(wallet: Arc<Mutex<crate::WalletManager>>) -> Self {
        Self { wallet }
    }
}

impl HostSigner for WalletPathSigner {
    fn public_key(&self) -> Result<[u8; 32], SignerError> {
        self.wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .chat_identity()
            .map_err(|e| map_wallet_err(e, 0))
    }

    fn sign(&self, payload: &[u8]) -> Result<[u8; 64], SignerError> {
        let sig = self
            .wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .chat_sign(payload)
            .map_err(|e| map_wallet_err(e, payload.len()))?;
        let mut arr = [0u8; 64];
        arr.copy_from_slice(&sig);
        Ok(arr)
    }
}

/// Signs with the `//wallet//sso` keypair used for SSO pairing identity.
///
/// Distinct from `WalletPathSigner` which uses `//wallet` (chat identity).
/// The SSO path is `//wallet//sso` — used as the HMAC key for pairing
/// channel derivation and session binding.
pub struct SsoPathSigner {
    wallet: Arc<Mutex<crate::WalletManager>>,
}

impl std::fmt::Debug for SsoPathSigner {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("SsoPathSigner").finish_non_exhaustive()
    }
}

impl SsoPathSigner {
    /// Create an `SsoPathSigner` from a shared wallet.
    pub fn new(wallet: Arc<Mutex<crate::WalletManager>>) -> Self {
        Self { wallet }
    }
}

impl HostSigner for SsoPathSigner {
    fn public_key(&self) -> Result<[u8; 32], SignerError> {
        self.wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .pairing_identity()
            .map_err(|e| map_wallet_err(e, 0))
    }

    fn sign(&self, payload: &[u8]) -> Result<[u8; 64], SignerError> {
        let sig = self
            .wallet
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .sso_sign(payload)
            .map_err(|e| map_wallet_err(e, payload.len()))?;
        let mut arr = [0u8; 64];
        arr.copy_from_slice(&sig);
        Ok(arr)
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use schnorrkel::{signing_context, PublicKey, Signature};

    const TEST_PHRASE: &str =
        "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";

    fn make_wallet() -> Arc<Mutex<crate::WalletManager>> {
        let mut wm = crate::WalletManager::new();
        wm.load_from_mnemonic(TEST_PHRASE).unwrap();
        Arc::new(Mutex::new(wm))
    }

    fn make_locked_wallet() -> Arc<Mutex<crate::WalletManager>> {
        Arc::new(Mutex::new(crate::WalletManager::new()))
    }

    // -- RootSigner --

    #[test]
    fn test_root_signer_public_key() {
        let signer = RootSigner::new(make_wallet());
        let pk = signer.public_key().unwrap();
        assert_ne!(pk, [0u8; 32]);
    }

    #[test]
    fn test_root_signer_sign_and_verify() {
        let signer = RootSigner::new(make_wallet());
        let pk_bytes = signer.public_key().unwrap();
        let sig_bytes = signer.sign(b"hello").unwrap();

        let pk = PublicKey::from_bytes(&pk_bytes).unwrap();
        let sig = Signature::from_bytes(&sig_bytes).unwrap();
        let ctx = signing_context(crate::SIGNING_CTX);
        assert!(pk.verify(ctx.bytes(b"hello"), &sig).is_ok());
    }

    #[test]
    fn test_root_signer_locked_errors() {
        let signer = RootSigner::new(make_locked_wallet());
        assert!(matches!(signer.public_key(), Err(SignerError::Locked)));
        assert!(matches!(signer.sign(b"hello"), Err(SignerError::Locked)));
    }

    #[test]
    fn test_root_signer_payload_too_large() {
        let signer = RootSigner::new(make_wallet());
        let huge = vec![0u8; crate::MAX_SIGN_PAYLOAD + 1];
        assert!(matches!(
            signer.sign(&huge),
            Err(SignerError::PayloadTooLarge { .. })
        ));
    }

    #[test]
    fn test_root_signer_empty_payload() {
        let signer = RootSigner::new(make_wallet());
        let sig = signer.sign(b"").unwrap();
        assert_eq!(sig.len(), 64);
    }

    #[test]
    fn test_root_signer_max_payload_succeeds() {
        let signer = RootSigner::new(make_wallet());
        let max = vec![0u8; crate::MAX_SIGN_PAYLOAD];
        let sig = signer.sign(&max).unwrap();
        assert_eq!(sig.len(), 64);
    }

    // -- AppSigner --

    #[test]
    fn test_app_signer_public_key_differs_from_root() {
        let wallet = make_wallet();
        let root = RootSigner::new(Arc::clone(&wallet));
        let app = AppSigner::new(Arc::clone(&wallet), "com.test.app".into(), 0);

        let root_pk = root.public_key().unwrap();
        let app_pk = app.public_key().unwrap();
        assert_ne!(root_pk, app_pk);
    }

    #[test]
    fn test_app_signer_different_indices_differ() {
        let wallet = make_wallet();
        let app0 = AppSigner::new(Arc::clone(&wallet), "com.test.app".into(), 0);
        let app1 = AppSigner::new(Arc::clone(&wallet), "com.test.app".into(), 1);
        assert_ne!(app0.public_key().unwrap(), app1.public_key().unwrap());
    }

    #[test]
    fn test_app_signer_sign_and_verify() {
        let signer = AppSigner::new(make_wallet(), "com.test.app".into(), 0);
        let pk_bytes = signer.public_key().unwrap();
        let sig_bytes = signer.sign(b"app payload").unwrap();

        let pk = PublicKey::from_bytes(&pk_bytes).unwrap();
        let sig = Signature::from_bytes(&sig_bytes).unwrap();
        let ctx = signing_context(crate::SIGNING_CTX);
        assert!(pk.verify(ctx.bytes(b"app payload"), &sig).is_ok());
    }

    #[test]
    fn test_app_signer_locked_errors() {
        let signer = AppSigner::new(make_locked_wallet(), "com.test.app".into(), 0);
        assert!(matches!(signer.public_key(), Err(SignerError::Locked)));
        assert!(matches!(signer.sign(b"hello"), Err(SignerError::Locked)));
    }

    #[test]
    fn test_app_signer_payload_too_large() {
        let signer = AppSigner::new(make_wallet(), "com.test.app".into(), 0);
        let huge = vec![0u8; crate::MAX_SIGN_PAYLOAD + 1];
        assert!(matches!(
            signer.sign(&huge),
            Err(SignerError::PayloadTooLarge { .. })
        ));
    }

    // -- WalletPathSigner --

    #[test]
    fn test_wallet_path_signer_public_key() {
        let signer = WalletPathSigner::new(make_wallet());
        let pk = signer.public_key().unwrap();
        assert_ne!(pk, [0u8; 32]);
    }

    #[test]
    fn test_wallet_path_signer_differs_from_root() {
        let wallet = make_wallet();
        let root = RootSigner::new(Arc::clone(&wallet));
        let wallet_path = WalletPathSigner::new(Arc::clone(&wallet));

        let root_pk = root.public_key().unwrap();
        let wallet_pk = wallet_path.public_key().unwrap();
        assert_ne!(root_pk, wallet_pk);
    }

    #[test]
    fn test_wallet_path_signer_sign_and_verify() {
        let signer = WalletPathSigner::new(make_wallet());
        let pk_bytes = signer.public_key().unwrap();
        let sig_bytes = signer.sign(b"chat message").unwrap();

        let pk = PublicKey::from_bytes(&pk_bytes).unwrap();
        let sig = Signature::from_bytes(&sig_bytes).unwrap();
        let ctx = signing_context(crate::SIGNING_CTX);
        assert!(pk.verify(ctx.bytes(b"chat message"), &sig).is_ok());
    }

    #[test]
    fn test_wallet_path_signer_locked_errors() {
        let signer = WalletPathSigner::new(make_locked_wallet());
        assert!(matches!(signer.public_key(), Err(SignerError::Locked)));
        assert!(matches!(signer.sign(b"hello"), Err(SignerError::Locked)));
    }

    #[test]
    fn test_wallet_path_signer_payload_too_large() {
        let signer = WalletPathSigner::new(make_wallet());
        let huge = vec![0u8; crate::MAX_SIGN_PAYLOAD + 1];
        assert!(matches!(
            signer.sign(&huge),
            Err(SignerError::PayloadTooLarge { .. })
        ));
    }

    // -- Object safety --

    #[test]
    fn test_host_signer_is_object_safe() {
        let wallet = make_wallet();
        let signers: Vec<Arc<dyn HostSigner>> = vec![
            Arc::new(RootSigner::new(Arc::clone(&wallet))),
            Arc::new(AppSigner::new(Arc::clone(&wallet), "app".into(), 0)),
            Arc::new(WalletPathSigner::new(Arc::clone(&wallet))),
        ];
        for s in &signers {
            let pk = s.public_key().unwrap();
            assert_ne!(pk, [0u8; 32]);
            let sig = s.sign(b"test").unwrap();
            assert_eq!(sig.len(), 64);
        }
    }

    // -- Public key stability --

    #[test]
    fn test_public_key_is_stable_across_calls() {
        let wallet = make_wallet();
        let signer = RootSigner::new(Arc::clone(&wallet));
        let pk1 = signer.public_key().unwrap();
        let pk2 = signer.public_key().unwrap();
        assert_eq!(pk1, pk2);
    }

    // -- Debug --

    #[test]
    fn test_debug_impls() {
        let wallet = make_wallet();
        let root = RootSigner::new(Arc::clone(&wallet));
        let app = AppSigner::new(Arc::clone(&wallet), "com.test".into(), 0);
        let wp = WalletPathSigner::new(Arc::clone(&wallet));
        assert!(format!("{root:?}").contains("RootSigner"));
        assert!(format!("{app:?}").contains("com.test"));
        assert!(format!("{wp:?}").contains("WalletPathSigner"));
    }
}
