"""
Tests for the wallet module.
Tests encryption, Bitcoin service, and wallet manager functionality.
"""
import pytest
import tempfile
import os
import sys
from unittest.mock import patch

# Add src to path for imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))

from wallet.encryption import KeyEncryption
from wallet.bitcoin_service import BitcoinService
from wallet.wallet_manager import WalletManager, BALANCE_CAP_SATOSHIS


class TestKeyEncryption:
    """Tests for KeyEncryption."""

    def test_encrypt_decrypt(self):
        """Test basic encryption and decryption."""
        keys = {1: 'test_master_key_v1'}
        encryption = KeyEncryption(keys)

        original = "my_secret_private_key"
        encrypted = encryption.encrypt(original)
        decrypted = encryption.decrypt(encrypted, version=1)

        assert decrypted == original
        assert encrypted != original

    def test_versioned_keys(self):
        """Test encryption with multiple key versions."""
        keys = {1: 'key_v1', 2: 'key_v2'}
        encryption = KeyEncryption(keys)

        data = "secret_data"
        encrypted_v1 = encryption.encrypt(data, version=1)
        encrypted_v2 = encryption.encrypt(data, version=2)

        # Both should decrypt correctly with their respective versions
        assert encryption.decrypt(encrypted_v1, version=1) == data
        assert encryption.decrypt(encrypted_v2, version=2) == data

        # Encrypted data should be different for different versions
        assert encrypted_v1 != encrypted_v2

    def test_re_encrypt(self):
        """Test re-encryption from old version to new version."""
        keys = {1: 'old_key', 2: 'new_key'}
        encryption = KeyEncryption(keys)

        data = "secret_data"
        encrypted_v1 = encryption.encrypt(data, version=1)

        # Re-encrypt to version 2
        encrypted_v2, new_version = encryption.re_encrypt(encrypted_v1, old_version=1, new_version=2)

        assert new_version == 2
        assert encryption.decrypt(encrypted_v2, version=2) == data

    def test_unknown_version_raises(self):
        """Test that decrypting with unknown version raises."""
        keys = {1: 'test_key'}
        encryption = KeyEncryption(keys)

        with pytest.raises(ValueError, match="Unknown encryption key version"):
            encryption.decrypt("some_data", version=99)


class TestBitcoinService:
    """Tests for BitcoinService."""

    def test_generate_keypair_testnet(self):
        """Test generating testnet keypair."""
        service = BitcoinService(testnet=True)
        keypair = service.generate_keypair()

        assert 'address' in keypair
        assert 'public_key' in keypair
        assert 'private_key' in keypair

        # Testnet addresses start with m, n, or tb1
        address = keypair['address']
        assert address.startswith(('m', 'n', 'tb1'))

    def test_validate_testnet_address(self):
        """Test validating testnet addresses."""
        service = BitcoinService(testnet=True)

        # Generate a valid testnet address
        keypair = service.generate_keypair()
        assert service.validate_address(keypair['address'])

        # Invalid addresses
        assert not service.validate_address('')
        assert not service.validate_address(None)
        assert not service.validate_address('invalid')
        assert not service.validate_address('1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')  # Mainnet

    def test_validate_mainnet_address(self):
        """Test validating mainnet addresses."""
        service = BitcoinService(testnet=False)

        # Valid mainnet addresses
        assert service.validate_address('1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2')
        assert service.validate_address('3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy')
        assert service.validate_address('bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq')

        # Testnet addresses should be invalid on mainnet
        assert not service.validate_address('mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt')

    def test_estimate_fee(self):
        """Test fee estimation."""
        service = BitcoinService(testnet=True)

        fee = service.estimate_fee(num_inputs=1, num_outputs=2)
        assert fee > 0
        assert isinstance(fee, int)

        # More inputs/outputs should cost more
        fee_larger = service.estimate_fee(num_inputs=3, num_outputs=5)
        assert fee_larger > fee


class MockDatabase:
    """Mock database for testing WalletManager."""

    def __init__(self):
        self.tables = {}
        self.data = {}

    def execute_sql(self, sql, params=None, fetch_one=False, fetch_all=False):
        """Simple mock SQL execution."""
        sql_lower = sql.lower().strip()

        if sql_lower.startswith('create table') or sql_lower.startswith('create index'):
            # Table creation - just ignore
            return None

        if sql_lower.startswith('alter table'):
            # Migration - add sweep_address column
            return None

        if sql_lower.startswith('insert'):
            # Store insert data
            if 'wallets' in sql_lower:
                # Two-address model: params are (user_id, btc_address, public_key, private_key, sweep_address, version, network)
                self.data.setdefault('wallets', {})[params[0]] = {
                    'user_id': params[0],
                    'btc_address': params[1],
                    'public_key_encrypted': params[2],
                    'private_key_encrypted': params[3],
                    'sweep_address': params[4] if len(params) > 6 else None,
                    'encryption_key_version': params[5] if len(params) > 6 else params[4],
                    'network': params[6] if len(params) > 6 else params[5],
                    'is_disabled': 0,
                    'created_at': 'now'
                }
            elif 'credits_ledger' in sql_lower:
                self.data.setdefault('credits_ledger', []).append({
                    'user_id': params[0],
                    'amount_satoshis': params[1],
                    'tx_type': params[2],
                    'reference_id': params[3],
                    'idempotency_key': params[-1],
                })
            elif 'pending_strategy_fees' in sql_lower:
                self.data.setdefault('pending_fees', {})[params[0]] = {
                    'strategy_run_id': params[0],
                    'user_id': params[1],
                    'creator_user_id': params[2],
                    'fee_percent': params[3] if len(params) > 3 else 10,
                    'accumulated_satoshis': 0,
                    'trade_count': 0,
                }
            elif 'withdrawal_requests' in sql_lower:
                self.data.setdefault('withdrawals', []).append({
                    'user_id': params[0],
                    'amount_satoshis': params[1],
                    'destination_address': params[2],
                    'status': 'reserved',  # status is hardcoded in SQL
                })
            return None

        if sql_lower.startswith('select'):
            if fetch_one:
                if 'wallets' in sql_lower and 'user_id' in sql_lower:
                    wallet = self.data.get('wallets', {}).get(params[0])
                    if wallet:
                        # Check if it's the get_wallet_keys query (includes encrypted keys)
                        if 'private_key_encrypted' in sql_lower:
                            return (wallet['btc_address'], wallet['public_key_encrypted'],
                                    wallet['private_key_encrypted'], wallet['encryption_key_version'],
                                    wallet['network'])
                        # Regular get_wallet query (5 columns including sweep_address)
                        return (wallet['btc_address'], wallet['network'],
                                wallet['is_disabled'], wallet['created_at'],
                                wallet.get('sweep_address'))
                elif 'credits_ledger' in sql_lower and 'sum' in sql_lower:
                    ledger = self.data.get('credits_ledger', [])
                    user_total = sum(e['amount_satoshis'] for e in ledger if e['user_id'] == params[0])
                    return (user_total,)
                elif 'credits_ledger' in sql_lower and 'idempotency' in sql_lower:
                    ledger = self.data.get('credits_ledger', [])
                    for entry in ledger:
                        if entry.get('idempotency_key') == params[0]:
                            return (1,)
                    return None
                elif 'pending_strategy_fees' in sql_lower:
                    fees = self.data.get('pending_fees', {}).get(params[0])
                    if fees:
                        # Different queries select different columns
                        if 'fee_percent' in sql_lower and 'accumulated' not in sql_lower:
                            # accumulate_trade_fee: SELECT fee_percent
                            return (fees.get('fee_percent', 10),)
                        elif 'accumulated_satoshis, trade_count' in sql_lower and 'user_id' not in sql_lower.split('select')[1].split('from')[0]:
                            # get_pending_fees: SELECT accumulated_satoshis, trade_count
                            return (fees['accumulated_satoshis'], fees['trade_count'])
                        else:
                            # settle_accumulated_fees: SELECT user_id, creator_user_id, accumulated_satoshis, trade_count
                            return (fees['user_id'], fees['creator_user_id'],
                                    fees['accumulated_satoshis'], fees['trade_count'])
                return None

            if fetch_all:
                if 'credits_ledger' in sql_lower:
                    ledger = self.data.get('credits_ledger', [])
                    user_entries = [e for e in ledger if e['user_id'] == params[0]]
                    return [(e['tx_type'], e['amount_satoshis'], e['reference_id'], 'now')
                            for e in user_entries[-params[1]:]]
                return []

        if sql_lower.startswith('update'):
            if 'wallets' in sql_lower and 'is_disabled' in sql_lower:
                user_id = params[0]  # Only param is user_id
                if user_id in self.data.get('wallets', {}):
                    # Parse the value from the SQL itself (is_disabled = 0 or is_disabled = 1)
                    if 'is_disabled = 1' in sql_lower:
                        self.data['wallets'][user_id]['is_disabled'] = 1
                    else:
                        self.data['wallets'][user_id]['is_disabled'] = 0
            elif 'pending_strategy_fees' in sql_lower and 'accumulated_satoshis' in sql_lower:
                # Update: accumulated_satoshis = accumulated_satoshis + ?, trade_count = trade_count + 1
                fee_amount = params[0]
                run_id = params[1]
                if run_id in self.data.get('pending_fees', {}):
                    self.data['pending_fees'][run_id]['accumulated_satoshis'] += fee_amount
                    self.data['pending_fees'][run_id]['trade_count'] += 1
            return None

        if sql_lower.startswith('delete'):
            if 'pending_strategy_fees' in sql_lower:
                run_id = params[0]
                if run_id in self.data.get('pending_fees', {}):
                    del self.data['pending_fees'][run_id]
            return None

        return None

    def execute_in_transaction(self, statements):
        """Execute multiple statements atomically (mock version)."""
        # In mock, just execute each statement in order
        for sql, params in statements:
            self.execute_sql(sql, params)
        return True


class TestWalletManager:
    """Tests for WalletManager."""

    @pytest.fixture
    def wallet_manager(self):
        """Create a wallet manager with mock database."""
        db = MockDatabase()
        keys = {1: 'test_encryption_key'}
        return WalletManager(db, keys, default_network='testnet')

    def test_create_wallet(self, wallet_manager):
        """Test creating a new wallet (two-address model)."""
        result = wallet_manager.create_wallet(user_id=1)

        assert result['success']
        # Fee Address (we store keys)
        assert 'fee_address' in result
        assert 'fee_private_key' in result
        # Sweep Address (we don't store private key)
        assert 'sweep_address' in result
        assert 'sweep_private_key' in result
        assert result['network'] == 'testnet'

    def test_create_wallet_duplicate(self, wallet_manager):
        """Test creating duplicate wallet fails."""
        wallet_manager.create_wallet(user_id=1)
        result = wallet_manager.create_wallet(user_id=1)

        assert not result['success']
        assert 'already exists' in result['error'].lower()

    def test_get_wallet(self, wallet_manager):
        """Test getting wallet info."""
        wallet_manager.create_wallet(user_id=1)
        wallet = wallet_manager.get_wallet(user_id=1)

        assert wallet is not None
        assert 'address' in wallet
        assert wallet['network'] == 'testnet'
        assert 'private_key' not in wallet  # Private key should not be exposed

    def test_credits_balance_empty(self, wallet_manager):
        """Test credits balance starts at zero."""
        balance = wallet_manager.get_credits_balance(user_id=1)
        assert balance == 0

    def test_admin_credit(self, wallet_manager):
        """Test admin credit adds to balance."""
        wallet_manager.admin_credit(user_id=1, amount_satoshis=10000, reason='test')
        balance = wallet_manager.get_credits_balance(user_id=1)
        assert balance == 10000

    def test_credit_deposit(self, wallet_manager):
        """Test deposit crediting."""
        result = wallet_manager.credit_deposit(
            user_id=1,
            amount_satoshis=50000,
            network='testnet',
            tx_hash='abc123',
            vout=0
        )

        assert result['success']
        assert wallet_manager.get_credits_balance(user_id=1) == 50000

    def test_deposit_idempotency(self, wallet_manager):
        """Test deposit is idempotent."""
        wallet_manager.credit_deposit(
            user_id=1, amount_satoshis=50000,
            network='testnet', tx_hash='abc123', vout=0
        )
        wallet_manager.credit_deposit(
            user_id=1, amount_satoshis=50000,
            network='testnet', tx_hash='abc123', vout=0
        )

        # Should only be credited once
        balance = wallet_manager.get_credits_balance(user_id=1)
        assert balance == 50000

    def test_fee_accumulation(self, wallet_manager):
        """Test fee accumulation during strategy run with default 10% fee."""
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='initial')

        # Start accumulation with default 10% fee
        result = wallet_manager.start_fee_accumulation(
            strategy_run_id='run_001',
            user_id=1,
            creator_user_id=2,
            fee_percent=10,  # 10% of exchange fee
            estimated_trades=5
        )
        assert result['success']

        # Accumulate some fees
        wallet_manager.accumulate_trade_fee('run_001', exchange_fee_satoshis=10000, is_profitable=True)
        wallet_manager.accumulate_trade_fee('run_001', exchange_fee_satoshis=5000, is_profitable=True)
        wallet_manager.accumulate_trade_fee('run_001', exchange_fee_satoshis=8000, is_profitable=False)  # Should not accumulate

        # Check pending fees
        pending = wallet_manager.get_pending_fees('run_001')
        assert pending['accumulated_satoshis'] == 1500  # 10% of 10000 + 10% of 5000
        assert pending['trade_count'] == 2  # Only profitable trades counted

    def test_fee_accumulation_custom_percent(self, wallet_manager):
        """Test fee accumulation with custom fee percentage (50%)."""
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='initial')

        # Start accumulation with 50% fee
        result = wallet_manager.start_fee_accumulation(
            strategy_run_id='run_002',
            user_id=1,
            creator_user_id=2,
            fee_percent=50,  # 50% of exchange fee
            estimated_trades=5
        )
        assert result['success']

        # Accumulate fee: 50% of 10000 = 5000
        wallet_manager.accumulate_trade_fee('run_002', exchange_fee_satoshis=10000, is_profitable=True)

        pending = wallet_manager.get_pending_fees('run_002')
        assert pending['accumulated_satoshis'] == 5000  # 50% of 10000
        assert pending['trade_count'] == 1

    def test_fee_accumulation_100_percent(self, wallet_manager):
        """Test fee accumulation at 100% (same as exchange commission)."""
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='initial')

        # Start accumulation with 100% fee
        result = wallet_manager.start_fee_accumulation(
            strategy_run_id='run_003',
            user_id=1,
            creator_user_id=2,
            fee_percent=100,  # 100% of exchange fee
            estimated_trades=5
        )
        assert result['success']

        # Accumulate fee: 100% of 10000 = 10000
        wallet_manager.accumulate_trade_fee('run_003', exchange_fee_satoshis=10000, is_profitable=True)

        pending = wallet_manager.get_pending_fees('run_003')
        assert pending['accumulated_satoshis'] == 10000  # 100% of 10000
        assert pending['trade_count'] == 1

    def test_fee_settlement(self, wallet_manager):
        """Test fee settlement when strategy stops."""
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='initial')
        wallet_manager.admin_credit(user_id=2, amount_satoshis=0, reason='creator setup')

        wallet_manager.start_fee_accumulation('run_001', user_id=1, creator_user_id=2)
        wallet_manager.accumulate_trade_fee('run_001', exchange_fee_satoshis=10000, is_profitable=True)

        # Settle fees
        result = wallet_manager.settle_accumulated_fees('run_001')

        assert result['success']
        assert result['settled'] == 1000  # 10% of 10000
        assert result['trades'] == 1

        # Check balances
        user_balance = wallet_manager.get_credits_balance(user_id=1)
        creator_balance = wallet_manager.get_credits_balance(user_id=2)

        assert user_balance == 99000  # 100000 - 1000
        assert creator_balance == 1000  # Received fee

    def test_insufficient_credits_for_strategy(self, wallet_manager):
        """Test that strategy start fails with insufficient credits."""
        # User has no credits
        result = wallet_manager.start_fee_accumulation(
            strategy_run_id='run_001',
            user_id=1,
            creator_user_id=2,
            estimated_trades=10
        )

        assert not result['success']
        assert 'insufficient' in result['error'].lower()

    def test_withdrawal_request(self, wallet_manager):
        """Test withdrawal request."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=50000, reason='test')

        result = wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt'
        )

        assert result['success']

        # Balance should be reduced immediately
        balance = wallet_manager.get_credits_balance(user_id=1)
        assert balance == 40000

    def test_withdrawal_insufficient_balance(self, wallet_manager):
        """Test withdrawal fails with insufficient balance."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=5000, reason='test')

        result = wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt'
        )

        assert not result['success']
        assert 'insufficient' in result['error'].lower()

    def test_withdrawal_invalid_address(self, wallet_manager):
        """Test withdrawal fails with invalid address."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=50000, reason='test')

        result = wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='invalid_address'
        )

        assert not result['success']
        assert 'invalid' in result['error'].lower()

    def test_withdrawal_reservation_transaction_failure(self, wallet_manager):
        """Test withdrawal reservation fails cleanly when transaction fails."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=50000, reason='test')

        def fail_transaction(_statements):
            raise RuntimeError('db transaction failed')

        wallet_manager.db.execute_in_transaction = fail_transaction

        result = wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt'
        )

        assert not result['success']
        assert 'failed to queue' in result['error'].lower()
        # Balance should remain unchanged because reservation wasn't committed.
        assert wallet_manager.get_credits_balance(user_id=1) == 50000

    def test_own_strategy_no_fees(self, wallet_manager):
        """Test that running own strategy doesn't require fees."""
        result = wallet_manager.start_fee_accumulation(
            strategy_run_id='run_001',
            user_id=1,
            creator_user_id=1,  # Same user
            estimated_trades=10
        )

        assert result['success']
        assert 'no fees' in result.get('message', '').lower()

    def test_transaction_history(self, wallet_manager):
        """Test getting transaction history."""
        wallet_manager.admin_credit(user_id=1, amount_satoshis=10000, reason='credit1')
        wallet_manager.admin_credit(user_id=1, amount_satoshis=5000, reason='credit2')

        history = wallet_manager.get_transaction_history(user_id=1, limit=10)

        assert len(history) == 2
        assert all('type' in tx for tx in history)
        assert all('amount' in tx for tx in history)


class TestBalanceCap:
    """Tests for balance cap functionality."""

    @pytest.fixture
    def wallet_manager(self):
        db = MockDatabase()
        keys = {1: 'test_key'}
        return WalletManager(db, keys, default_network='testnet')

    def test_balance_cap_disables_wallet(self, wallet_manager):
        """Test that exceeding balance cap disables wallet."""
        wallet_manager.create_wallet(user_id=1)

        # Credit above cap
        wallet_manager.admin_credit(
            user_id=1,
            amount_satoshis=BALANCE_CAP_SATOSHIS + 10000,
            reason='over cap'
        )

        result = wallet_manager.check_balance_cap(user_id=1)
        assert result['over_cap']

        # Wallet should show as disabled
        wallet = wallet_manager.get_wallet(user_id=1)
        assert wallet['is_disabled']

    def test_balance_under_cap_enables_wallet(self, wallet_manager):
        """Test that being under balance cap enables wallet."""
        wallet_manager.create_wallet(user_id=1)

        # Credit under cap
        wallet_manager.admin_credit(
            user_id=1,
            amount_satoshis=BALANCE_CAP_SATOSHIS - 10000,
            reason='under cap'
        )

        result = wallet_manager.check_balance_cap(user_id=1)
        assert not result['over_cap']

        wallet = wallet_manager.get_wallet(user_id=1)
        assert not wallet['is_disabled']


class MockDatabaseForBackgroundJobs(MockDatabase):
    """Extended mock database for testing background job methods."""

    def execute_sql(self, sql, params=None, fetch_one=False, fetch_all=False):
        sql_lower = sql.lower().strip()

        # Handle get_wallets_over_cap query
        if 'left join credits_ledger' in sql_lower and 'having balance' in sql_lower:
            results = []
            for user_id, wallet in self.data.get('wallets', {}).items():
                if not wallet.get('sweep_address'):
                    continue
                # Calculate balance from ledger
                ledger = self.data.get('credits_ledger', [])
                balance = sum(e['amount_satoshis'] for e in ledger if e['user_id'] == user_id)
                if balance > params[0]:  # params[0] is BALANCE_CAP_SATOSHIS
                    results.append((user_id, wallet.get('sweep_address'), balance))
            if fetch_all:
                return results
            return None

        # Handle get_all_active_wallets query
        if 'select user_id, btc_address, network' in sql_lower and 'is_disabled = 0' in sql_lower:
            results = []
            for user_id, wallet in self.data.get('wallets', {}).items():
                if not wallet.get('is_disabled'):
                    results.append((user_id, wallet['btc_address'], wallet['network']))
            if fetch_all:
                return results
            return None

        # Handle get_wallets_for_deposit_monitoring query (includes disabled wallets)
        if 'select user_id, btc_address, network' in sql_lower and 'from wallets' in sql_lower and 'is_disabled' not in sql_lower:
            results = []
            for user_id, wallet in self.data.get('wallets', {}).items():
                results.append((user_id, wallet['btc_address'], wallet['network']))
            if fetch_all:
                return results
            return None

        # Handle deposits table queries
        if 'insert' in sql_lower and 'deposits' in sql_lower:
            self.data.setdefault('deposits', []).append({
                'user_id': params[0],
                'network': params[1],
                'tx_hash': params[2],
                'vout': params[3],
                'amount_satoshis': params[4],
                'credited': params[5] if len(params) > 5 else 0
            })
            return None

        if 'select' in sql_lower and 'deposits' in sql_lower and 'network' in sql_lower and fetch_one:
            deposits = self.data.get('deposits', [])
            for d in deposits:
                if d['network'] == params[0] and d['tx_hash'] == params[1] and d['vout'] == params[2]:
                    return (d.get('id', 1),)
            return None

        # Handle get_pending_withdrawals query
        if 'withdrawal_requests' in sql_lower and "status = 'reserved'" in sql_lower and fetch_all:
            withdrawals = self.data.get('withdrawals', [])
            results = []
            for i, w in enumerate(withdrawals):
                if w.get('status') == 'reserved':
                    results.append((i + 1, w['user_id'], w['amount_satoshis'], w['destination_address']))
            return results

        # Handle withdrawal_requests SELECT for process_withdrawal and _fail_withdrawal
        if 'withdrawal_requests' in sql_lower and 'select' in sql_lower and 'where id' in sql_lower and fetch_one:
            withdrawals = self.data.get('withdrawals', [])
            withdrawal_id = params[0]
            if withdrawal_id > 0 and withdrawal_id <= len(withdrawals):
                w = withdrawals[withdrawal_id - 1]
                # process_withdrawal query includes status, _fail_withdrawal does not
                if 'status' in sql_lower:
                    return (w['user_id'], w['amount_satoshis'], w['destination_address'], w.get('status', 'reserved'))
                else:
                    return (w['user_id'], w['amount_satoshis'], w['destination_address'])
            return None

        # Handle withdrawal_requests UPDATE
        if 'update' in sql_lower and 'withdrawal_requests' in sql_lower:
            withdrawal_id = params[-1]  # Last param is the ID in WHERE clause
            withdrawals = self.data.get('withdrawals', [])
            if withdrawal_id > 0 and withdrawal_id <= len(withdrawals):
                if 'processing' in sql_lower:
                    withdrawals[withdrawal_id - 1]['status'] = 'processing'
                elif 'completed' in sql_lower:
                    withdrawals[withdrawal_id - 1]['status'] = 'completed'
                    withdrawals[withdrawal_id - 1]['btc_txhash'] = params[0]
                elif 'failed' in sql_lower:
                    withdrawals[withdrawal_id - 1]['status'] = 'failed'
                    withdrawals[withdrawal_id - 1]['error_message'] = params[0]
            return None

        # Fall back to parent implementation
        return super().execute_sql(sql, params, fetch_one, fetch_all)


class TestBackgroundJobSupport:
    """Tests for background job support methods in WalletManager."""

    @pytest.fixture
    def wallet_manager(self):
        db = MockDatabaseForBackgroundJobs()
        keys = {1: 'test_key'}
        return WalletManager(db, keys, default_network='testnet')

    def test_get_wallets_over_cap(self, wallet_manager):
        """Test getting wallets with balance over cap."""
        # Create wallet with sweep address
        wallet_manager.create_wallet(user_id=1)

        # Credit above cap
        wallet_manager.admin_credit(
            user_id=1,
            amount_satoshis=BALANCE_CAP_SATOSHIS + 50000,
            reason='over cap'
        )

        wallets = wallet_manager.get_wallets_over_cap()

        assert len(wallets) == 1
        assert wallets[0]['user_id'] == 1
        assert wallets[0]['balance'] == BALANCE_CAP_SATOSHIS + 50000
        assert wallets[0]['sweep_address'] is not None

    def test_get_wallets_over_cap_empty(self, wallet_manager):
        """Test getting wallets when none are over cap."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=10000, reason='under cap')

        wallets = wallet_manager.get_wallets_over_cap()
        assert len(wallets) == 0

    def test_get_all_active_wallets(self, wallet_manager):
        """Test getting all active (non-disabled) wallets."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.create_wallet(user_id=2)

        # Disable user 2's wallet
        wallet_manager.db.data['wallets'][2]['is_disabled'] = 1

        wallets = wallet_manager.get_all_active_wallets()

        assert len(wallets) == 1
        assert wallets[0]['user_id'] == 1

    def test_get_wallets_for_deposit_monitoring_includes_disabled(self, wallet_manager):
        """Deposit monitoring should include disabled wallets."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.create_wallet(user_id=2)
        wallet_manager.db.data['wallets'][2]['is_disabled'] = 1

        wallets = wallet_manager.get_wallets_for_deposit_monitoring()
        user_ids = {w['user_id'] for w in wallets}

        assert user_ids == {1, 2}

    def test_get_pending_withdrawals(self, wallet_manager):
        """Test getting pending withdrawal requests."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='test')

        # Request withdrawal
        wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt'
        )

        pending = wallet_manager.get_pending_withdrawals()

        assert len(pending) == 1
        assert pending[0]['user_id'] == 1
        assert pending[0]['amount_satoshis'] == 10000

    def test_auto_sweep_no_wallet(self, wallet_manager):
        """Test auto_sweep fails when no wallet exists."""
        result = wallet_manager.auto_sweep(user_id=999, amount_satoshis=10000)

        assert not result['success']
        assert 'no wallet' in result['error'].lower()

    def test_auto_sweep_no_sweep_address(self, wallet_manager):
        """Test auto_sweep fails when no sweep address configured."""
        wallet_manager.create_wallet(user_id=1)
        # Remove sweep address
        wallet_manager.db.data['wallets'][1]['sweep_address'] = None

        result = wallet_manager.auto_sweep(user_id=1, amount_satoshis=10000)

        assert not result['success']
        assert 'sweep address' in result['error'].lower()

    def test_process_withdrawal_not_found(self, wallet_manager):
        """Test processing non-existent withdrawal."""
        result = wallet_manager.process_withdrawal(withdrawal_id=999)

        assert not result['success']
        assert 'not found' in result['error'].lower()

    def test_fail_withdrawal_reverses_credit(self, wallet_manager):
        """Test that failing a withdrawal reverses the ledger debit."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='test')

        # Request withdrawal (debits 10000)
        wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt'
        )

        # Balance should be reduced
        assert wallet_manager.get_credits_balance(user_id=1) == 90000

        # Fail the withdrawal
        wallet_manager._fail_withdrawal(withdrawal_id=1, error_message='Test failure')

        # Balance should be restored
        assert wallet_manager.get_credits_balance(user_id=1) == 100000

    def test_process_withdrawal_none_txhash_reverses_credit(self, wallet_manager):
        """None tx hash from send_transaction should fail and reverse reservation."""
        wallet_manager.create_wallet(user_id=1)
        wallet_manager.admin_credit(user_id=1, amount_satoshis=100000, reason='test')
        wallet_manager.request_withdrawal(
            user_id=1,
            amount_satoshis=10000,
            destination_address='mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt'
        )
        assert wallet_manager.get_credits_balance(user_id=1) == 90000

        with patch('wallet.wallet_manager.BitcoinService.send_transaction', return_value=None):
            result = wallet_manager.process_withdrawal(withdrawal_id=1)

        assert not result['success']
        assert 'no tx hash' in result['error'].lower()
        assert wallet_manager.get_credits_balance(user_id=1) == 100000
