//! Wallet Database Tests
//!
//! This module contains generic tests for wallet database implementations.
//! These tests can be used to verify any wallet database implementation
//! by using the `wallet_db_test!` macro.
#![allow(clippy::unwrap_used, clippy::missing_panics_doc)]

use std::collections::{BTreeMap, HashMap};
use std::str::FromStr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};

use cashu::nut00::KnownMethod;
use cashu::secret::Secret;
use cashu::{Amount, CurrencyUnit, MeltQuoteState, MintQuoteState, SecretKey};

use super::*;
use crate::mint_url::MintUrl;
use crate::nuts::{Id, KeySetInfo, Keys, MintInfo, Proof, State};
use crate::wallet::{
    MeltQuote, MintQuote, OperationData, ProofInfo, SwapOperationData, SwapSagaState, Transaction,
    TransactionDirection, WalletSaga, WalletSagaState,
};

static COUNTER: AtomicU64 = AtomicU64::new(0);

/// Generate a unique test ID
fn unique_id() -> String {
    let now = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_nanos();
    let n = COUNTER.fetch_add(1, Ordering::Relaxed);
    format!("test_{}_{}", now, n)
}

/// Generate valid test keys and return both the keys and the matching keyset ID.
/// The keyset ID is derived from the keys using the v1 algorithm:
fn test_keys_with_id() -> (Keys, Id) {
    // Generate deterministic keys for amounts 1, 2, 4, 8
    let mut keys_map = BTreeMap::new();

    // Use deterministic secret keys for reproducibility
    let secret_bytes: [[u8; 32]; 4] = [[1u8; 32], [2u8; 32], [4u8; 32], [8u8; 32]];

    for (i, amount) in [1u64, 2, 4, 8].iter().enumerate() {
        let sk = SecretKey::from_slice(&secret_bytes[i]).expect("valid secret key");
        let pk = sk.public_key();
        keys_map.insert(Amount::from(*amount), pk);
    }

    let keys = Keys::new(keys_map);
    let id = Id::v1_from_keys(&keys);

    (keys, id)
}

/// Generate a unique test keyset ID
fn test_keyset_id() -> Id {
    Id::from_str("00916bbf7ef91a36").unwrap()
}

/// Generate a second test keyset ID
fn test_keyset_id_2() -> Id {
    Id::from_str("00916bbf7ef91a37").unwrap()
}

/// Create a test mint URL
fn test_mint_url() -> MintUrl {
    MintUrl::from_str("https://test-mint.example.com").unwrap()
}

/// Create a second test mint URL
fn test_mint_url_2() -> MintUrl {
    MintUrl::from_str("https://test-mint-2.example.com").unwrap()
}

/// Create test keyset info
fn test_keyset_info(keyset_id: Id, _mint_url: &MintUrl) -> KeySetInfo {
    KeySetInfo {
        id: keyset_id,
        unit: CurrencyUnit::Sat,
        active: true,
        input_fee_ppk: 0,
        final_expiry: None,
    }
}

/// Create a test proof
fn test_proof(keyset_id: Id, amount: u64) -> Proof {
    Proof {
        amount: Amount::from(amount),
        keyset_id,
        secret: Secret::generate(),
        c: SecretKey::generate().public_key(),
        witness: None,
        dleq: None,
    }
}

/// Create test proof info
fn test_proof_info(keyset_id: Id, amount: u64, mint_url: MintUrl) -> ProofInfo {
    let proof = test_proof(keyset_id, amount);
    ProofInfo::new(proof, mint_url, State::Unspent, CurrencyUnit::Sat).unwrap()
}

/// Create a test mint quote
fn test_mint_quote(mint_url: MintUrl) -> MintQuote {
    MintQuote::new(
        unique_id(),
        mint_url,
        cashu::PaymentMethod::Known(KnownMethod::Bolt11),
        Some(Amount::from(1000)),
        CurrencyUnit::Sat,
        "lnbc1000...".to_string(),
        9999999999,
        None,
    )
}

/// Create a test melt quote
fn test_melt_quote() -> MeltQuote {
    MeltQuote {
        id: unique_id(),
        unit: CurrencyUnit::Sat,
        amount: Amount::from(1000),
        request: "lnbc1000...".to_string(),
        fee_reserve: Amount::from(10),
        state: cashu::MeltQuoteState::Unpaid,
        expiry: 9999999999,
        payment_preimage: None,
        payment_method: cashu::PaymentMethod::Known(KnownMethod::Bolt11),
        used_by_operation: None,
        version: 0,
    }
}

/// Create a test transaction
fn test_transaction(mint_url: MintUrl, direction: TransactionDirection) -> Transaction {
    let ys = vec![SecretKey::generate().public_key()];
    Transaction {
        mint_url,
        direction,
        amount: Amount::from(100),
        fee: Amount::from(1),
        unit: CurrencyUnit::Sat,
        ys,
        timestamp: 1234567890,
        memo: Some("test transaction".to_string()),
        metadata: HashMap::new(),
        quote_id: None,
        payment_request: None,
        payment_proof: None,
        payment_method: None,
        saga_id: None,
    }
}

/// Create a test wallet saga
fn test_wallet_saga(mint_url: MintUrl) -> WalletSaga {
    WalletSaga::new(
        uuid::Uuid::new_v4(),
        WalletSagaState::Swap(SwapSagaState::ProofsReserved),
        Amount::from(1000),
        mint_url,
        CurrencyUnit::Sat,
        OperationData::Swap(SwapOperationData {
            input_amount: Amount::from(1000),
            output_amount: Amount::from(990),
            counter_start: Some(0),
            counter_end: Some(10),
            blinded_messages: None,
        }),
    )
}

// =============================================================================
// Mint Management Tests
// =============================================================================

/// Test adding and retrieving a mint
pub async fn add_and_get_mint<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let mint_info = MintInfo::default();

    // Add mint
    db.add_mint(mint_url.clone(), Some(mint_info.clone()))
        .await
        .unwrap();

    // Get mint
    let retrieved = db.get_mint(mint_url.clone()).await.unwrap();
    assert!(retrieved.is_some());

    // Get all mints
    let mints = db.get_mints().await.unwrap();
    assert!(mints.contains_key(&mint_url));
}

/// Test adding mint without info
pub async fn add_mint_without_info<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();

    db.add_mint(mint_url.clone(), None).await.unwrap();

    // Verify mint exists in the database
    let mints = db.get_mints().await.unwrap();
    assert!(mints.contains_key(&mint_url));
}

/// Test removing a mint
pub async fn remove_mint<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();

    // Add mint
    db.add_mint(mint_url.clone(), None).await.unwrap();

    // Remove mint
    db.remove_mint(mint_url.clone()).await.unwrap();

    let result = db.get_mint(mint_url).await.unwrap();
    assert!(result.is_none());
}

/// Test updating mint URL
pub async fn update_mint_url<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let old_url = test_mint_url();
    let new_url = test_mint_url_2();

    // Add mint with old URL
    db.add_mint(old_url.clone(), None).await.unwrap();

    // Update URL
    db.update_mint_url(old_url.clone(), new_url.clone())
        .await
        .unwrap();
}

// =============================================================================
// Keyset Management Tests
// =============================================================================

/// Test adding and retrieving keysets
pub async fn add_and_get_keysets<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let keyset_info = test_keyset_info(keyset_id, &mint_url);

    // Add mint first
    db.add_mint(mint_url.clone(), None).await.unwrap();
    db.add_mint_keysets(mint_url.clone(), vec![keyset_info.clone()])
        .await
        .unwrap();

    // Get keyset by ID
    let retrieved = db.get_keyset_by_id(&keyset_id).await.unwrap();
    assert!(retrieved.is_some());
    assert_eq!(retrieved.unwrap().id, keyset_id);

    // Get keysets for mint
    let keysets = db.get_mint_keysets(mint_url).await.unwrap();
    assert!(keysets.is_some());
    assert!(!keysets.unwrap().is_empty());
}

/// Test getting keyset by ID in transaction
pub async fn get_keyset_by_id_in_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let keyset_info = test_keyset_info(keyset_id, &mint_url);

    // Add keyset
    db.add_mint(mint_url.clone(), None).await.unwrap();
    db.add_mint_keysets(mint_url.clone(), vec![keyset_info])
        .await
        .unwrap();

    // Get in transaction
    let retrieved = db.get_keyset_by_id(&keyset_id).await.unwrap();
    assert!(retrieved.is_some());
}

/// Test adding and retrieving keys
pub async fn add_and_get_keys<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Generate valid keys with matching keyset ID
    let (keys, keyset_id) = test_keys_with_id();
    let keyset = cashu::KeySet {
        id: keyset_id,
        unit: CurrencyUnit::Sat,
        active: None,
        keys: keys.clone(),
        input_fee_ppk: 0,
        final_expiry: None,
    };

    // Add keys
    db.add_keys(keyset).await.unwrap();

    // Get keys
    let retrieved = db.get_keys(&keyset_id).await.unwrap();
    assert!(retrieved.is_some());
    let retrieved_keys = retrieved.unwrap();
    assert_eq!(retrieved_keys.len(), keys.len());
}

/// Test getting keys in transaction
pub async fn get_keys_in_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Generate valid keys with matching keyset ID
    let (keys, keyset_id) = test_keys_with_id();
    let keyset = cashu::KeySet {
        id: keyset_id,
        unit: CurrencyUnit::Sat,
        active: None,
        keys: keys.clone(),
        input_fee_ppk: 0,
        final_expiry: None,
    };

    // Add keys
    db.add_keys(keyset).await.unwrap();

    // Get in transaction
    let retrieved = db.get_keys(&keyset_id).await.unwrap();
    assert!(retrieved.is_some());
    let retrieved_keys = retrieved.unwrap();
    assert_eq!(retrieved_keys.len(), keys.len());
}

/// Test removing keys
pub async fn remove_keys<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Generate valid keys with matching keyset ID
    let (keys, keyset_id) = test_keys_with_id();
    let keyset = cashu::KeySet {
        id: keyset_id,
        unit: CurrencyUnit::Sat,
        active: None,
        keys,
        input_fee_ppk: 0,
        final_expiry: None,
    };

    // Add keys
    db.add_keys(keyset).await.unwrap();

    // Verify keys were added
    let retrieved = db.get_keys(&keyset_id).await.unwrap();
    assert!(retrieved.is_some());

    // Remove keys
    db.remove_keys(&keyset_id).await.unwrap();

    let retrieved = db.get_keys(&keyset_id).await.unwrap();
    assert!(retrieved.is_none());
}

// =============================================================================
// Mint Quote Tests
// =============================================================================

/// Test adding and retrieving mint quotes
pub async fn add_and_get_mint_quote<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let quote = test_mint_quote(mint_url);

    // Add quote
    db.add_mint_quote(quote.clone()).await.unwrap();

    // Get quote
    let retrieved = db.get_mint_quote(&quote.id).await.unwrap();
    assert!(retrieved.is_some());
    assert_eq!(retrieved.unwrap().id, quote.id);

    // Get all quotes
    let quotes = db.get_mint_quotes().await.unwrap();
    assert!(!quotes.is_empty());
}

/// Test getting mint quote in transaction
pub async fn get_mint_quote_in_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let quote = test_mint_quote(mint_url);

    // Add quote
    db.add_mint_quote(quote.clone()).await.unwrap();

    // Get in transaction
    let retrieved = db.get_mint_quote(&quote.id).await.unwrap();
    assert!(retrieved.is_some());
}

/// Test removing mint quote
pub async fn remove_mint_quote<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let quote = test_mint_quote(mint_url);

    // Add quote
    db.add_mint_quote(quote.clone()).await.unwrap();

    // Remove quote
    db.remove_mint_quote(&quote.id).await.unwrap();

    let retrieved = db.get_mint_quote(&quote.id).await.unwrap();
    assert!(retrieved.is_none());
}

// =============================================================================
// Melt Quote Tests
// =============================================================================

/// Test adding and retrieving melt quotes
pub async fn add_and_get_melt_quote<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let quote = test_melt_quote();

    // Add quote
    db.add_melt_quote(quote.clone()).await.unwrap();

    // Get quote
    let retrieved = db.get_melt_quote(&quote.id).await.unwrap();
    assert!(retrieved.is_some());
    assert_eq!(retrieved.unwrap().id, quote.id);

    // Get all quotes
    let quotes = db.get_melt_quotes().await.unwrap();
    assert!(!quotes.is_empty());
}

/// Test getting melt quote in transaction
pub async fn get_melt_quote_in_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let quote = test_melt_quote();

    // Add quote
    db.add_melt_quote(quote.clone()).await.unwrap();

    // Get in transaction
    let retrieved = db.get_melt_quote(&quote.id).await.unwrap();
    assert!(retrieved.is_some());
}

/// Test removing melt quote
pub async fn remove_melt_quote<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let quote = test_melt_quote();

    // Add quote
    db.add_melt_quote(quote.clone()).await.unwrap();

    // Remove quote
    db.remove_melt_quote(&quote.id).await.unwrap();

    let retrieved = db.get_melt_quote(&quote.id).await.unwrap();
    assert!(retrieved.is_none());
}

/// Test mint quote optimistic locking
pub async fn add_mint_quote_optimistic_locking<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let quote = test_mint_quote(mint_url);

    // 1. Initial add (insert)
    db.add_mint_quote(quote.clone()).await.unwrap();

    let retrieved = db.get_mint_quote(&quote.id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 0);

    // 2. Update (version 0 -> 1)
    let mut quote_update_1 = quote.clone();
    quote_update_1.state = MintQuoteState::Issued; // Change something
    db.add_mint_quote(quote_update_1.clone()).await.unwrap();

    let retrieved = db.get_mint_quote(&quote.id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 1);
    assert_eq!(retrieved.state, MintQuoteState::Issued);

    // 3. Stale update (using version 0) - should fail
    // quote_update_1 still has version 0
    let mut stale_quote = quote_update_1.clone();
    stale_quote.amount = Some(Amount::from(999));

    let result = db.add_mint_quote(stale_quote).await;
    assert!(matches!(
        result,
        Err(crate::database::Error::ConcurrentUpdate)
    ));

    // Verify DB wasn't changed
    let retrieved = db.get_mint_quote(&quote.id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 1);
    assert_eq!(retrieved.state, MintQuoteState::Issued);
    assert_ne!(retrieved.amount, Some(Amount::from(999)));
}

/// Test melt quote optimistic locking
pub async fn add_melt_quote_optimistic_locking<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let quote = test_melt_quote();

    // 1. Initial add (insert)
    db.add_melt_quote(quote.clone()).await.unwrap();

    let retrieved = db.get_melt_quote(&quote.id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 0);

    // 2. Update (version 0 -> 1)
    let mut quote_update_1 = quote.clone();
    quote_update_1.state = MeltQuoteState::Paid;
    db.add_melt_quote(quote_update_1.clone()).await.unwrap();

    let retrieved = db.get_melt_quote(&quote.id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 1);
    assert_eq!(retrieved.state, MeltQuoteState::Paid);

    // 3. Stale update (using version 0) - should fail
    let mut stale_quote = quote_update_1.clone();
    stale_quote.amount = Amount::from(999);

    let result = db.add_melt_quote(stale_quote).await;
    assert!(matches!(
        result,
        Err(crate::database::Error::ConcurrentUpdate)
    ));

    // Verify DB wasn't changed
    let retrieved = db.get_melt_quote(&quote.id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 1);
    assert_eq!(retrieved.state, MeltQuoteState::Paid);
    assert_ne!(retrieved.amount, Amount::from(999));
}

// =============================================================================
// Proof Management Tests
// =============================================================================

/// Test adding and retrieving proofs
pub async fn add_and_get_proofs<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Get proofs
    let proofs = db.get_proofs(None, None, None, None).await.unwrap();
    assert!(!proofs.is_empty());

    // Get proofs by mint URL
    let proofs = db
        .get_proofs(Some(mint_url.clone()), None, None, None)
        .await
        .unwrap();
    assert!(!proofs.is_empty());

    // Get proofs by Y
    let ys = vec![proof_info.y];
    let proofs = db.get_proofs_by_ys(ys).await.unwrap();
    assert!(!proofs.is_empty());
}

/// Test getting proofs in transaction
pub async fn get_proofs_in_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Get proofs in transaction
    let proofs = db.get_proofs(None, None, None, None).await.unwrap();
    assert!(!proofs.is_empty());
}

/// Test updating proofs (add and remove)
pub async fn update_proofs<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info_1 = test_proof_info(keyset_id, 100, mint_url.clone());
    let proof_info_2 = test_proof_info(keyset_id, 200, mint_url.clone());

    // Add first proof
    db.update_proofs(vec![proof_info_1.clone()], vec![])
        .await
        .unwrap();

    // Add second, remove first
    db.update_proofs(vec![proof_info_2.clone()], vec![proof_info_1.y])
        .await
        .unwrap();

    // Verify
    let proofs = db.get_proofs(None, None, None, None).await.unwrap();
    assert_eq!(proofs.len(), 1);
    assert_eq!(proofs[0].y, proof_info_2.y);
}

/// Test updating proofs state
pub async fn update_proofs_state<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Update state
    db.update_proofs_state(vec![proof_info.y], State::Pending)
        .await
        .unwrap();

    // Verify
    let proofs = db
        .get_proofs(None, None, Some(vec![State::Pending]), None)
        .await
        .unwrap();
    assert!(!proofs.is_empty());
}

/// Test filtering proofs by unit
pub async fn filter_proofs_by_unit<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Filter by unit
    let proofs = db
        .get_proofs(None, Some(CurrencyUnit::Sat), None, None)
        .await
        .unwrap();
    assert!(!proofs.is_empty());

    // Filter by different unit
    let proofs = db
        .get_proofs(None, Some(CurrencyUnit::Msat), None, None)
        .await
        .unwrap();
    assert!(proofs.is_empty());
}

/// Test filtering proofs by state
pub async fn filter_proofs_by_state<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Filter by state
    let proofs = db
        .get_proofs(None, None, Some(vec![State::Unspent]), None)
        .await
        .unwrap();
    assert!(!proofs.is_empty());

    // Filter by different state
    let proofs = db
        .get_proofs(None, None, Some(vec![State::Spent]), None)
        .await
        .unwrap();
    assert!(proofs.is_empty());
}

// =============================================================================
// Balance Tests
// =============================================================================

/// Test getting balance
pub async fn get_balance<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info_1 = test_proof_info(keyset_id, 100, mint_url.clone());
    let proof_info_2 = test_proof_info(keyset_id, 200, mint_url.clone());

    // Add proofs
    db.update_proofs(vec![proof_info_1, proof_info_2], vec![])
        .await
        .unwrap();

    // Get total balance
    let balance = db.get_balance(None, None, None).await.unwrap();
    assert_eq!(balance, 300);

    // Get balance by mint
    let balance = db.get_balance(Some(mint_url), None, None).await.unwrap();
    assert_eq!(balance, 300);
}

/// Test getting balance by state
pub async fn get_balance_by_state<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Get balance by state
    let balance = db
        .get_balance(None, None, Some(vec![State::Unspent]))
        .await
        .unwrap();
    assert_eq!(balance, 100);

    // Get balance by different state
    let balance = db
        .get_balance(None, None, Some(vec![State::Spent]))
        .await
        .unwrap();
    assert_eq!(balance, 0);
}

// =============================================================================
// Keyset Counter Tests
// =============================================================================

/// Test incrementing keyset counter
pub async fn increment_keyset_counter<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let keyset_id = test_keyset_id();

    // Increment counter
    let counter1 = db.increment_keyset_counter(&keyset_id, 5).await.unwrap();

    assert_eq!(counter1, 5);

    // Increment again
    let counter2 = db.increment_keyset_counter(&keyset_id, 10).await.unwrap();

    assert_eq!(counter2, 15);
}

/// Test keyset counter isolation between keysets
pub async fn keyset_counter_isolation<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let keyset_id_1 = test_keyset_id();
    let keyset_id_2 = test_keyset_id_2();

    // Increment first keyset
    db.increment_keyset_counter(&keyset_id_1, 5).await.unwrap();

    // Increment second keyset
    let counter2 = db.increment_keyset_counter(&keyset_id_2, 10).await.unwrap();

    // Second keyset should start from 0
    assert_eq!(counter2, 10);

    // First keyset should still be at 5
    let counter1 = db.increment_keyset_counter(&keyset_id_1, 0).await.unwrap();

    assert_eq!(counter1, 5);
}

// =============================================================================
// Transaction Tests
// =============================================================================

/// Test adding and retrieving transactions
pub async fn add_and_get_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let transaction = test_transaction(mint_url.clone(), TransactionDirection::Incoming);
    let tx_id = transaction.id();

    // Add transaction
    db.add_transaction(transaction.clone()).await.unwrap();

    // Get transaction
    let retrieved = db.get_transaction(tx_id).await.unwrap();
    assert!(retrieved.is_some());
    assert_eq!(retrieved.unwrap().id(), tx_id);
}

/// Test listing transactions
pub async fn list_transactions<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let tx_incoming = test_transaction(mint_url.clone(), TransactionDirection::Incoming);
    let tx_outgoing = test_transaction(mint_url.clone(), TransactionDirection::Outgoing);

    // Add transactions
    db.add_transaction(tx_incoming).await.unwrap();
    db.add_transaction(tx_outgoing).await.unwrap();

    // List all
    let transactions = db.list_transactions(None, None, None).await.unwrap();
    assert_eq!(transactions.len(), 2);

    // List by direction
    let incoming = db
        .list_transactions(None, Some(TransactionDirection::Incoming), None)
        .await
        .unwrap();
    assert_eq!(incoming.len(), 1);

    let outgoing = db
        .list_transactions(None, Some(TransactionDirection::Outgoing), None)
        .await
        .unwrap();
    assert_eq!(outgoing.len(), 1);
}

/// Test filtering transactions by mint
pub async fn filter_transactions_by_mint<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url_1 = test_mint_url();
    let mint_url_2 = test_mint_url_2();
    let tx_1 = test_transaction(mint_url_1.clone(), TransactionDirection::Incoming);
    let tx_2 = test_transaction(mint_url_2.clone(), TransactionDirection::Incoming);

    // Add transactions
    db.add_transaction(tx_1).await.unwrap();
    db.add_transaction(tx_2).await.unwrap();

    // Filter by mint
    let transactions = db
        .list_transactions(Some(mint_url_1), None, None)
        .await
        .unwrap();
    assert_eq!(transactions.len(), 1);
}

/// Test removing transaction
pub async fn remove_transaction<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let transaction = test_transaction(mint_url, TransactionDirection::Incoming);
    let tx_id = transaction.id();

    // Add transaction
    db.add_transaction(transaction).await.unwrap();

    // Remove transaction
    db.remove_transaction(tx_id).await.unwrap();

    let retrieved = db.get_transaction(tx_id).await.unwrap();
    assert!(retrieved.is_none());
}

// KV Store Tests
// =============================================================================

/// Test KV store write and read operations
pub async fn kvstore_write_and_read<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Write some test data
    db.kv_write("test_namespace", "sub_namespace", "key1", b"value1")
        .await
        .unwrap();
    db.kv_write("test_namespace", "sub_namespace", "key2", b"value2")
        .await
        .unwrap();
    db.kv_write("test_namespace", "other_sub", "key3", b"value3")
        .await
        .unwrap();

    // Read back the data
    let value1 = db
        .kv_read("test_namespace", "sub_namespace", "key1")
        .await
        .unwrap();
    assert_eq!(value1, Some(b"value1".to_vec()));

    let value2 = db
        .kv_read("test_namespace", "sub_namespace", "key2")
        .await
        .unwrap();
    assert_eq!(value2, Some(b"value2".to_vec()));

    let value3 = db
        .kv_read("test_namespace", "other_sub", "key3")
        .await
        .unwrap();
    assert_eq!(value3, Some(b"value3".to_vec()));

    // Read non-existent key
    let missing = db
        .kv_read("test_namespace", "sub_namespace", "missing")
        .await
        .unwrap();
    assert_eq!(missing, None);
}

/// Test KV store list operation
pub async fn kvstore_list<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Write some test data
    db.kv_write("test_namespace", "sub_namespace", "key1", b"value1")
        .await
        .unwrap();
    db.kv_write("test_namespace", "sub_namespace", "key2", b"value2")
        .await
        .unwrap();
    db.kv_write("test_namespace", "other_sub", "key3", b"value3")
        .await
        .unwrap();

    // List keys in namespace
    let mut keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
    keys.sort();
    assert_eq!(keys, vec!["key1", "key2"]);

    // List keys in other namespace
    let other_keys = db.kv_list("test_namespace", "other_sub").await.unwrap();
    assert_eq!(other_keys, vec!["key3"]);

    // List keys in empty namespace
    let empty_keys = db.kv_list("test_namespace", "empty_sub").await.unwrap();
    assert!(empty_keys.is_empty());
}

/// Test KV store update operation
pub async fn kvstore_update<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Write initial value
    db.kv_write("test_namespace", "sub_namespace", "key1", b"value1")
        .await
        .unwrap();

    // Verify initial value
    let value = db
        .kv_read("test_namespace", "sub_namespace", "key1")
        .await
        .unwrap();
    assert_eq!(value, Some(b"value1".to_vec()));

    // Update value
    db.kv_write("test_namespace", "sub_namespace", "key1", b"updated_value1")
        .await
        .unwrap();

    // Verify updated value
    let value = db
        .kv_read("test_namespace", "sub_namespace", "key1")
        .await
        .unwrap();
    assert_eq!(value, Some(b"updated_value1".to_vec()));
}

/// Test KV store remove operation
pub async fn kvstore_remove<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Write some test data
    db.kv_write("test_namespace", "sub_namespace", "key1", b"value1")
        .await
        .unwrap();
    db.kv_write("test_namespace", "sub_namespace", "key2", b"value2")
        .await
        .unwrap();

    // Verify data exists
    let keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
    assert_eq!(keys.len(), 2);

    // Remove one key
    db.kv_remove("test_namespace", "sub_namespace", "key1")
        .await
        .unwrap();

    // Verify key is removed
    let value = db
        .kv_read("test_namespace", "sub_namespace", "key1")
        .await
        .unwrap();
    assert_eq!(value, None);

    // Verify other key still exists
    let value = db
        .kv_read("test_namespace", "sub_namespace", "key2")
        .await
        .unwrap();
    assert_eq!(value, Some(b"value2".to_vec()));

    // Verify list is updated
    let keys = db.kv_list("test_namespace", "sub_namespace").await.unwrap();
    assert_eq!(keys, vec!["key2"]);
}

/// Test KV store namespace isolation
pub async fn kvstore_namespace_isolation<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    // Write same key to different namespaces
    db.kv_write("ns1", "sub", "key", b"value_ns1")
        .await
        .unwrap();
    db.kv_write("ns2", "sub", "key", b"value_ns2")
        .await
        .unwrap();
    db.kv_write("ns1", "sub2", "key", b"value_sub2")
        .await
        .unwrap();

    // Verify isolation by primary namespace
    let value1 = db.kv_read("ns1", "sub", "key").await.unwrap();
    assert_eq!(value1, Some(b"value_ns1".to_vec()));

    let value2 = db.kv_read("ns2", "sub", "key").await.unwrap();
    assert_eq!(value2, Some(b"value_ns2".to_vec()));

    // Verify isolation by secondary namespace
    let value3 = db.kv_read("ns1", "sub2", "key").await.unwrap();
    assert_eq!(value3, Some(b"value_sub2".to_vec()));

    // Remove from one namespace shouldn't affect others
    db.kv_remove("ns1", "sub", "key").await.unwrap();

    let value1 = db.kv_read("ns1", "sub", "key").await.unwrap();
    assert_eq!(value1, None);

    let value2 = db.kv_read("ns2", "sub", "key").await.unwrap();
    assert_eq!(value2, Some(b"value_ns2".to_vec()));

    let value3 = db.kv_read("ns1", "sub2", "key").await.unwrap();
    assert_eq!(value3, Some(b"value_sub2".to_vec()));
}

// =============================================================================
// Wallet Saga Tests
// =============================================================================

/// Test adding and retrieving a saga
pub async fn add_and_get_saga<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let saga = test_wallet_saga(mint_url);
    let saga_id = saga.id;

    // Add saga
    db.add_saga(saga.clone()).await.unwrap();

    // Get saga
    let retrieved = db.get_saga(&saga_id).await.unwrap();
    assert!(retrieved.is_some());
    let retrieved = retrieved.unwrap();
    assert_eq!(retrieved.id, saga_id);
    assert_eq!(retrieved.version, 0);
    assert_eq!(retrieved.amount, Amount::from(1000));
}

/// Test saga optimistic locking
pub async fn update_saga_optimistic_locking<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let saga = test_wallet_saga(mint_url);
    let saga_id = saga.id;

    // Add saga
    db.add_saga(saga).await.unwrap();

    // Get saga and update state
    let mut saga = db.get_saga(&saga_id).await.unwrap().unwrap();
    assert_eq!(saga.version, 0);

    saga.update_state(WalletSagaState::Swap(SwapSagaState::SwapRequested));
    assert_eq!(saga.version, 1);

    // Update should succeed
    let success = db.update_saga(saga.clone()).await.unwrap();
    assert!(success);

    // Verify updated state
    let retrieved = db.get_saga(&saga_id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 1);
    assert!(matches!(
        retrieved.state,
        WalletSagaState::Swap(SwapSagaState::SwapRequested)
    ));

    // Try to update with stale version (simulating concurrent access)
    let mut stale_saga = saga.clone();
    stale_saga.version = 0; // Reset to old version
    stale_saga.update_state(WalletSagaState::Swap(SwapSagaState::ProofsReserved));
    // Now version is 1, but DB has version 1, so expected_version would be 0

    // This should fail due to version mismatch
    let success = db.update_saga(stale_saga).await.unwrap();
    assert!(!success);

    // Original state should be unchanged
    let retrieved = db.get_saga(&saga_id).await.unwrap().unwrap();
    assert_eq!(retrieved.version, 1);
    assert!(matches!(
        retrieved.state,
        WalletSagaState::Swap(SwapSagaState::SwapRequested)
    ));
}

/// Test deleting a saga
pub async fn delete_saga<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let saga = test_wallet_saga(mint_url);
    let saga_id = saga.id;

    // Add saga
    db.add_saga(saga).await.unwrap();

    // Verify it exists
    let retrieved = db.get_saga(&saga_id).await.unwrap();
    assert!(retrieved.is_some());

    // Delete saga
    db.delete_saga(&saga_id).await.unwrap();

    // Verify it's gone
    let retrieved = db.get_saga(&saga_id).await.unwrap();
    assert!(retrieved.is_none());
}

/// Test getting incomplete sagas
pub async fn get_incomplete_sagas<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();

    // Add multiple sagas
    let saga1 = test_wallet_saga(mint_url.clone());
    let saga2 = test_wallet_saga(mint_url.clone());
    let saga3 = test_wallet_saga(mint_url);
    let saga1_id = saga1.id;
    let saga3_id = saga3.id;

    db.add_saga(saga1).await.unwrap();
    db.add_saga(saga2.clone()).await.unwrap();
    db.add_saga(saga3).await.unwrap();

    // Get all incomplete sagas
    let incomplete = db.get_incomplete_sagas().await.unwrap();
    assert_eq!(incomplete.len(), 3);

    // Delete one saga (simulating completion)
    db.delete_saga(&saga2.id).await.unwrap();

    // Should now have 2 incomplete
    let incomplete = db.get_incomplete_sagas().await.unwrap();
    assert_eq!(incomplete.len(), 2);

    // Verify the correct ones remain
    let ids: Vec<_> = incomplete.iter().map(|s| s.id).collect();
    assert!(ids.contains(&saga1_id));
    assert!(ids.contains(&saga3_id));
}

// =============================================================================
// Proof Reservation Tests
// =============================================================================

/// Test reserving proofs for an operation
pub async fn reserve_proofs<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info_1 = test_proof_info(keyset_id, 100, mint_url.clone());
    let proof_info_2 = test_proof_info(keyset_id, 200, mint_url.clone());

    // Add proofs
    db.update_proofs(vec![proof_info_1.clone(), proof_info_2.clone()], vec![])
        .await
        .unwrap();

    // Reserve proofs for an operation
    let operation_id = uuid::Uuid::new_v4();
    db.reserve_proofs(vec![proof_info_1.y, proof_info_2.y], &operation_id)
        .await
        .unwrap();

    // Verify proofs are now reserved
    let proofs = db
        .get_proofs(None, None, Some(vec![State::Reserved]), None)
        .await
        .unwrap();
    assert_eq!(proofs.len(), 2);
}

/// Test releasing reserved proofs
pub async fn release_proofs<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info_1 = test_proof_info(keyset_id, 100, mint_url.clone());
    let proof_info_2 = test_proof_info(keyset_id, 200, mint_url.clone());

    // Add proofs
    db.update_proofs(vec![proof_info_1.clone(), proof_info_2.clone()], vec![])
        .await
        .unwrap();

    // Reserve proofs
    let operation_id = uuid::Uuid::new_v4();
    db.reserve_proofs(vec![proof_info_1.y, proof_info_2.y], &operation_id)
        .await
        .unwrap();

    // Verify they're reserved
    let reserved = db
        .get_proofs(None, None, Some(vec![State::Reserved]), None)
        .await
        .unwrap();
    assert_eq!(reserved.len(), 2);

    // Release proofs
    db.release_proofs(&operation_id).await.unwrap();

    // Verify proofs are back to unspent
    let unspent = db
        .get_proofs(None, None, Some(vec![State::Unspent]), None)
        .await
        .unwrap();
    assert_eq!(unspent.len(), 2);

    let reserved = db
        .get_proofs(None, None, Some(vec![State::Reserved]), None)
        .await
        .unwrap();
    assert!(reserved.is_empty());
}

/// Test getting proofs reserved by an operation
pub async fn get_reserved_proofs<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info_1 = test_proof_info(keyset_id, 100, mint_url.clone());
    let proof_info_2 = test_proof_info(keyset_id, 200, mint_url.clone());
    let proof_info_3 = test_proof_info(keyset_id, 300, mint_url.clone());

    // Add proofs
    db.update_proofs(
        vec![
            proof_info_1.clone(),
            proof_info_2.clone(),
            proof_info_3.clone(),
        ],
        vec![],
    )
    .await
    .unwrap();

    // Reserve some proofs for operation 1
    let operation_id_1 = uuid::Uuid::new_v4();
    db.reserve_proofs(vec![proof_info_1.y, proof_info_2.y], &operation_id_1)
        .await
        .unwrap();

    // Reserve other proofs for operation 2
    let operation_id_2 = uuid::Uuid::new_v4();
    db.reserve_proofs(vec![proof_info_3.y], &operation_id_2)
        .await
        .unwrap();

    // Get proofs for operation 1
    let reserved_1 = db.get_reserved_proofs(&operation_id_1).await.unwrap();
    assert_eq!(reserved_1.len(), 2);
    let ys_1: Vec<_> = reserved_1.iter().map(|p| p.y).collect();
    assert!(ys_1.contains(&proof_info_1.y));
    assert!(ys_1.contains(&proof_info_2.y));

    // Get proofs for operation 2
    let reserved_2 = db.get_reserved_proofs(&operation_id_2).await.unwrap();
    assert_eq!(reserved_2.len(), 1);
    assert_eq!(reserved_2[0].y, proof_info_3.y);

    // Get proofs for non-existent operation
    let empty = db.get_reserved_proofs(&uuid::Uuid::new_v4()).await.unwrap();
    assert!(empty.is_empty());
}

/// Test that reserving already reserved proofs fails
pub async fn reserve_proofs_already_reserved<DB>(db: DB)
where
    DB: Database<crate::database::Error>,
{
    let mint_url = test_mint_url();
    let keyset_id = test_keyset_id();
    let proof_info = test_proof_info(keyset_id, 100, mint_url.clone());

    // Add proof
    db.update_proofs(vec![proof_info.clone()], vec![])
        .await
        .unwrap();

    // Reserve proof
    let operation_id_1 = uuid::Uuid::new_v4();
    db.reserve_proofs(vec![proof_info.y], &operation_id_1)
        .await
        .unwrap();

    // Try to reserve the same proof for another operation - should fail
    let operation_id_2 = uuid::Uuid::new_v4();
    let result = db.reserve_proofs(vec![proof_info.y], &operation_id_2).await;
    assert!(result.is_err());
}

/// Unit test that is expected to be passed for a correct wallet database implementation
#[macro_export]
macro_rules! wallet_db_test {
    ($make_db_fn:ident) => {
        wallet_db_test!(
            $make_db_fn,
            add_and_get_mint,
            add_mint_without_info,
            remove_mint,
            update_mint_url,
            add_and_get_keysets,
            get_keyset_by_id_in_transaction,
            add_and_get_keys,
            get_keys_in_transaction,
            remove_keys,
            add_and_get_mint_quote,
            get_mint_quote_in_transaction,
            remove_mint_quote,
            add_and_get_melt_quote,
            get_melt_quote_in_transaction,
            remove_melt_quote,
            add_mint_quote_optimistic_locking,
            add_melt_quote_optimistic_locking,
            add_and_get_proofs,
            get_proofs_in_transaction,
            update_proofs,
            update_proofs_state,
            filter_proofs_by_unit,
            filter_proofs_by_state,
            get_balance,
            get_balance_by_state,
            increment_keyset_counter,
            keyset_counter_isolation,
            add_and_get_transaction,
            list_transactions,
            filter_transactions_by_mint,
            remove_transaction,
            kvstore_write_and_read,
            kvstore_list,
            kvstore_update,
            kvstore_remove,
            kvstore_namespace_isolation,
            add_and_get_saga,
            update_saga_optimistic_locking,
            delete_saga,
            get_incomplete_sagas,
            reserve_proofs,
            release_proofs,
            get_reserved_proofs,
            reserve_proofs_already_reserved
        );
    };
    ($make_db_fn:ident, $($name:ident),+ $(,)?) => {
        ::paste::paste! {
            $(
                #[tokio::test]
                async fn [<wallet_ $name>]() {
                    use std::time::{SystemTime, UNIX_EPOCH};
                    let now = SystemTime::now()
                        .duration_since(UNIX_EPOCH)
                        .expect("Time went backwards");

                    cdk_common::database::wallet::test::$name($make_db_fn(format!("test_{}_{}", now.as_nanos(), stringify!($name))).await).await;
                }
            )+
        }
    };
}
