//! Negative security test: verify that key material never appears in tracing
//! output.
//!
//! This test instruments the wallet's logging and Debug paths and asserts that
//! mnemonics, private keys, and session keys are never present in any captured
//! output. Establishes a regression test for when wallet is instrumented with
//! tracing spans in Phase 2.
//!
//! Run: cargo test -p host-wallet --test tracing_no_secrets

use std::sync::{Arc, Mutex};
use tracing_subscriber::{layer::SubscriberExt, Layer};

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

/// Captures ALL string output from tracing (span names, field values, event messages).
struct OutputCapture {
    output: Arc<Mutex<Vec<String>>>,
}

impl<S: tracing::Subscriber> Layer<S> for OutputCapture {
    fn on_new_span(
        &self,
        attrs: &tracing::span::Attributes<'_>,
        _id: &tracing::span::Id,
        _ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        let mut visitor = StringCollector::default();
        attrs.record(&mut visitor);
        self.output
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .extend(visitor.strings);
    }

    fn on_event(
        &self,
        event: &tracing::Event<'_>,
        _ctx: tracing_subscriber::layer::Context<'_, S>,
    ) {
        let mut visitor = StringCollector::default();
        event.record(&mut visitor);
        self.output
            .lock()
            .unwrap_or_else(|e| e.into_inner())
            .extend(visitor.strings);
    }
}

#[derive(Default)]
struct StringCollector {
    strings: Vec<String>,
}

impl tracing::field::Visit for StringCollector {
    fn record_debug(&mut self, _field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
        self.strings.push(format!("{:?}", value));
    }
    fn record_str(&mut self, _field: &tracing::field::Field, value: &str) {
        self.strings.push(value.to_string());
    }
    fn record_u64(&mut self, _field: &tracing::field::Field, value: u64) {
        self.strings.push(value.to_string());
    }
    fn record_i64(&mut self, _field: &tracing::field::Field, value: i64) {
        self.strings.push(value.to_string());
    }
    fn record_bool(&mut self, _field: &tracing::field::Field, value: bool) {
        self.strings.push(value.to_string());
    }
}

#[test]
fn test_wallet_operations_never_leak_mnemonic() {
    let output: Arc<Mutex<Vec<String>>> = Arc::default();
    let layer = OutputCapture {
        output: output.clone(),
    };
    let subscriber = tracing_subscriber::registry().with(layer);

    tracing::subscriber::with_default(subscriber, || {
        let mut wallet = host_wallet::WalletManager::new();
        wallet
            .load_from_mnemonic(TEST_MNEMONIC)
            .expect("loading test mnemonic must succeed");

        // Exercise operations that touch key material.
        let _ = wallet.app_address("test-app", 0);
        let _ = wallet.sign("test-app", 0, b"test payload");

        // Debug impl should redact all keys.
        let _ = format!("{:?}", wallet);
    });

    let captured = output.lock().unwrap_or_else(|e| e.into_inner());
    let all_output = captured.join(" ");

    // Every word in the mnemonic must be absent from tracing output.
    for word in TEST_MNEMONIC.split_whitespace() {
        assert!(
            !all_output.contains(word),
            "SECURITY: mnemonic word '{word}' found in tracing output!"
        );
    }
}

#[test]
fn test_wallet_debug_redacts_keys() {
    let mut wallet = host_wallet::WalletManager::new();
    wallet
        .load_from_mnemonic(TEST_MNEMONIC)
        .expect("loading test mnemonic must succeed");

    let debug_output = format!("{:?}", wallet);

    // The Debug impl should only show safe metadata, not key bytes.
    assert!(
        debug_output.contains("has_mnemonic"),
        "Debug should include has_mnemonic field"
    );
    assert!(
        !debug_output.contains("root_keypair"),
        "Debug must not expose root_keypair"
    );
    assert!(
        !debug_output.contains("eth_key"),
        "Debug must not expose eth_key"
    );
    assert!(
        !debug_output.contains("btc_key"),
        "Debug must not expose btc_key"
    );
    assert!(
        !debug_output.contains("paired_session_key"),
        "Debug must not expose paired_session_key"
    );

    // Mnemonic words must not leak through Debug.
    for word in TEST_MNEMONIC.split_whitespace() {
        assert!(
            !debug_output.contains(word),
            "SECURITY: mnemonic word '{word}' found in Debug output!"
        );
    }
}
