"""Main wallet management operations using credits ledger with balance cap."""
import logging
import uuid
from typing import Optional

from .bitcoin_service import BitcoinService
from .encryption import KeyEncryption

logger = logging.getLogger(__name__)

# Balance cap in satoshis (~$50 at typical BTC prices)
# 0.001 BTC ~ $50-100 range depending on BTC price
BALANCE_CAP_SATOSHIS = 100000  # 0.001 BTC


class WalletManager:
    """
    Manages user Bitcoin wallets and internal credits ledger.

    Two-Address Custody Model:
    1. Fee Address: We store private key, used for strategy fees (max ~$50)
    2. Sweep Address: We only store public address, user controls private key

    Credits Ledger: Internal balance for instant, reliable strategy fees
    """

    # Balance cap as class attribute so background jobs can access it
    BALANCE_CAP_SATOSHIS = BALANCE_CAP_SATOSHIS

    def __init__(self, database, encryption_keys: dict, default_network: str = 'testnet'):
        """
        Initialize wallet manager.

        Args:
            database: Database instance for persistence.
            encryption_keys: Dict mapping version numbers to encryption keys.
            default_network: 'testnet' or 'mainnet' for new wallets.
        """
        self.db = database
        self.encryption = KeyEncryption(encryption_keys)
        self.default_network = default_network

        # Ensure wallet tables exist
        self._ensure_tables()

    def _ensure_tables(self):
        """Create wallet-related tables if they don't exist."""
        # Wallets table - stores Fee Address (with keys) and Sweep Address (public only)
        self.db.execute_sql("""
            CREATE TABLE IF NOT EXISTS wallets (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL UNIQUE,
                btc_address TEXT NOT NULL,
                public_key_encrypted TEXT NOT NULL,
                private_key_encrypted TEXT NOT NULL,
                sweep_address TEXT,
                encryption_key_version INTEGER NOT NULL DEFAULT 1,
                network TEXT NOT NULL DEFAULT 'testnet' CHECK (network IN ('testnet', 'mainnet')),
                is_disabled INTEGER NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users(id)
            )
        """)

        # Migration: Add sweep_address column if missing (for existing wallets)
        try:
            self.db.execute_sql("ALTER TABLE wallets ADD COLUMN sweep_address TEXT")
        except Exception:
            pass  # Column already exists

        # Credits ledger - source of truth for spendable balance
        self.db.execute_sql("""
            CREATE TABLE IF NOT EXISTS credits_ledger (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                amount_satoshis INTEGER NOT NULL CHECK (amount_satoshis != 0),
                tx_type TEXT NOT NULL CHECK (tx_type IN ('deposit', 'withdrawal', 'withdrawal_reversal', 'fee_paid', 'fee_received', 'admin_credit')),
                reference_id TEXT,
                counterparty_user_id INTEGER,
                idempotency_key TEXT NOT NULL UNIQUE,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users(id),
                FOREIGN KEY (counterparty_user_id) REFERENCES users(id)
            )
        """)

        # Pending fees during strategy run
        self.db.execute_sql("""
            CREATE TABLE IF NOT EXISTS pending_strategy_fees (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                strategy_run_id TEXT NOT NULL UNIQUE,
                user_id INTEGER NOT NULL,
                creator_user_id INTEGER NOT NULL,
                fee_percent INTEGER NOT NULL DEFAULT 10,
                accumulated_satoshis INTEGER NOT NULL DEFAULT 0,
                trade_count INTEGER NOT NULL DEFAULT 0,
                started_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users(id),
                FOREIGN KEY (creator_user_id) REFERENCES users(id)
            )
        """)

        # Migration: Add fee_percent column if missing
        try:
            self.db.execute_sql("ALTER TABLE pending_strategy_fees ADD COLUMN fee_percent INTEGER NOT NULL DEFAULT 10")
        except Exception:
            pass  # Column already exists

        # Withdrawal requests (async processing)
        self.db.execute_sql("""
            CREATE TABLE IF NOT EXISTS withdrawal_requests (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                amount_satoshis INTEGER NOT NULL CHECK (amount_satoshis > 0),
                destination_address TEXT NOT NULL,
                status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'reserved', 'processing', 'completed', 'failed', 'reversed')),
                btc_txhash TEXT,
                error_message TEXT,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                processed_at TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users(id)
            )
        """)

        # Deposit tracking (for dedupe)
        self.db.execute_sql("""
            CREATE TABLE IF NOT EXISTS deposits (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                user_id INTEGER NOT NULL,
                network TEXT NOT NULL,
                tx_hash TEXT NOT NULL,
                vout INTEGER NOT NULL,
                amount_satoshis INTEGER NOT NULL,
                confirmations INTEGER NOT NULL DEFAULT 0,
                credited INTEGER NOT NULL DEFAULT 0,
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (user_id) REFERENCES users(id)
            )
        """)

        # Create indexes for performance
        self.db.execute_sql("CREATE INDEX IF NOT EXISTS idx_wallets_user_id ON wallets(user_id)")
        self.db.execute_sql("CREATE INDEX IF NOT EXISTS idx_ledger_user_id ON credits_ledger(user_id)")
        self.db.execute_sql("CREATE INDEX IF NOT EXISTS idx_ledger_idempotency ON credits_ledger(idempotency_key)")
        self.db.execute_sql("CREATE INDEX IF NOT EXISTS idx_pending_fees_run ON pending_strategy_fees(strategy_run_id)")
        self.db.execute_sql("CREATE INDEX IF NOT EXISTS idx_withdrawals_status ON withdrawal_requests(status)")
        self.db.execute_sql("CREATE INDEX IF NOT EXISTS idx_deposits_credited ON deposits(credited)")
        # Unique constraint on deposits (network, tx_hash, vout)
        self.db.execute_sql("CREATE UNIQUE INDEX IF NOT EXISTS idx_deposits_unique ON deposits(network, tx_hash, vout)")

        logger.info("Wallet tables ensured")

    def create_wallet(self, user_id: int, user_sweep_address: str = None) -> dict:
        """
        Generate and store new wallet for user (two-address model).

        Creates:
        1. Fee Address - We store private key, used for strategy fees
        2. Sweep Address - Either user-provided or we generate one (we don't store private key)

        Args:
            user_id: User ID to create wallet for.
            user_sweep_address: Optional user-provided sweep address (e.g., from their own wallet).

        Returns:
            Dict with success status and both addresses' details.
            The sweep address private key is only returned if we generated it.
        """
        existing = self.get_wallet(user_id)
        if existing:
            return {'success': False, 'error': 'Wallet already exists'}

        bitcoin = BitcoinService(testnet=(self.default_network == 'testnet'))

        # Validate user-provided sweep address if given
        if user_sweep_address:
            if not bitcoin.validate_address(user_sweep_address):
                return {'success': False, 'error': 'Invalid Bitcoin address for sweep address'}

        # Generate Fee Address (we store and manage)
        fee_keypair = bitcoin.generate_keypair()

        # Sweep Address: use user-provided or generate new
        if user_sweep_address:
            sweep_address = user_sweep_address
            sweep_keypair = None  # User controls this, we don't have the keys
        else:
            # Generate Sweep Address (user controls - we DON'T store private key)
            sweep_keypair = bitcoin.generate_keypair()
            sweep_address = sweep_keypair['address']

        version = KeyEncryption.CURRENT_VERSION
        encrypted_public = self.encryption.encrypt(fee_keypair['public_key'], version)
        encrypted_private = self.encryption.encrypt(fee_keypair['private_key'], version)

        self.db.execute_sql(
            """INSERT INTO wallets (user_id, btc_address, public_key_encrypted,
               private_key_encrypted, sweep_address, encryption_key_version, network)
               VALUES (?, ?, ?, ?, ?, ?, ?)""",
            (user_id, fee_keypair['address'], encrypted_public, encrypted_private,
             sweep_address, version, self.default_network)
        )

        logger.info(f"Created two-address wallet for user {user_id}: "
                   f"fee={fee_keypair['address']}, sweep={sweep_address} "
                   f"(user_provided={bool(user_sweep_address)})")

        result = {
            'success': True,
            # Fee Address (we manage)
            'fee_address': fee_keypair['address'],
            'fee_public_key': fee_keypair['public_key'],
            'fee_private_key': fee_keypair['private_key'],
            # Sweep Address
            'sweep_address': sweep_address,
            'network': self.default_network,
        }

        # Only include sweep private key if we generated it
        if sweep_keypair:
            result['sweep_public_key'] = sweep_keypair['public_key']
            result['sweep_private_key'] = sweep_keypair['private_key']
            result['warning'] = 'SAVE YOUR SWEEP ADDRESS PRIVATE KEY. It is NOT stored and CANNOT be recovered.'
        else:
            result['message'] = 'Using your provided sweep address. Excess funds will be sent there.'

        return result

    def get_wallet(self, user_id: int) -> Optional[dict]:
        """
        Get wallet info (without decrypted private keys).

        Args:
            user_id: User ID to look up.

        Returns:
            Dict with wallet info, or None if no wallet exists.
        """
        result = self.db.execute_sql(
            "SELECT btc_address, network, is_disabled, created_at, sweep_address FROM wallets WHERE user_id = ?",
            (user_id,), fetch_one=True
        )
        if not result:
            return None
        return {
            'fee_address': result[0],
            'address': result[0],  # Backwards compatibility
            'network': result[1],
            'is_disabled': bool(result[2]),
            'created_at': result[3],
            'sweep_address': result[4]
        }

    def get_wallet_keys(self, user_id: int) -> Optional[dict]:
        """
        Get fee address keys (for "View Keys" feature).
        Note: Sweep address private key is NOT stored.

        Args:
            user_id: User ID to look up.

        Returns:
            Dict with fee address and decrypted private key, or None.
        """
        result = self.db.execute_sql(
            """SELECT btc_address, public_key_encrypted, private_key_encrypted,
                      encryption_key_version, network FROM wallets WHERE user_id = ?""",
            (user_id,), fetch_one=True
        )
        if not result:
            return None

        address, encrypted_public, encrypted_private, version, network = result

        try:
            private_key = self.encryption.decrypt(encrypted_private, version)
            return {
                'fee_address': address,
                'fee_private_key': private_key,
                'network': network
            }
        except Exception as e:
            logger.error(f"Failed to decrypt keys for user {user_id}: {e}")
            return None

    def get_credits_balance(self, user_id: int) -> int:
        """
        Get user's spendable credits balance (sum of ledger entries).

        Args:
            user_id: User ID to look up.

        Returns:
            Balance in satoshis.
        """
        result = self.db.execute_sql(
            "SELECT COALESCE(SUM(amount_satoshis), 0) FROM credits_ledger WHERE user_id = ?",
            (user_id,), fetch_one=True
        )
        return result[0] if result else 0

    def check_balance_cap(self, user_id: int) -> dict:
        """
        Check if balance exceeds cap and disable/enable wallet accordingly.

        Args:
            user_id: User ID to check.

        Returns:
            Dict with 'over_cap' status and current balance.
        """
        balance = self.get_credits_balance(user_id)
        if balance > BALANCE_CAP_SATOSHIS:
            self.db.execute_sql(
                "UPDATE wallets SET is_disabled = 1 WHERE user_id = ?", (user_id,)
            )
            logger.warning(f"User {user_id} wallet disabled - over balance cap "
                          f"({balance} > {BALANCE_CAP_SATOSHIS})")
            return {'over_cap': True, 'balance': balance, 'cap': BALANCE_CAP_SATOSHIS}
        else:
            # Re-enable if under cap
            self.db.execute_sql(
                "UPDATE wallets SET is_disabled = 0 WHERE user_id = ?", (user_id,)
            )
            return {'over_cap': False, 'balance': balance}

    # === Strategy Fee Accumulation ===

    def start_fee_accumulation(self, strategy_run_id: str, user_id: int,
                               creator_user_id: int, fee_percent: int = 10,
                               estimated_trades: int = 10) -> dict:
        """
        Start fee accumulation for a strategy run.
        Checks if user has enough credits for estimated trades.

        Args:
            strategy_run_id: Unique identifier for this strategy run.
            user_id: User running the strategy.
            creator_user_id: Strategy creator who will receive fees.
            fee_percent: Percentage of exchange commission to charge (1-100).
            estimated_trades: Estimated number of trades for credit check.

        Returns:
            Dict with success status.
        """
        # If user is running their own strategy, no fees apply
        if user_id == creator_user_id:
            return {'success': True, 'message': 'No fees for own strategy'}

        # Validate fee_percent
        fee_percent = max(1, min(100, int(fee_percent)))

        # Check wallet is enabled
        wallet = self.get_wallet(user_id)
        if wallet and wallet['is_disabled']:
            return {'success': False, 'error': 'Wallet disabled - withdraw funds to re-enable'}

        # Estimate fee per trade (rough estimate, actual depends on trade size)
        # Scale estimate by fee_percent (higher fee = more credits needed)
        base_estimate = 1000  # ~1000 sats per trade at 100%
        min_credits_needed = (base_estimate * fee_percent // 100) * estimated_trades

        balance = self.get_credits_balance(user_id)
        if balance < min_credits_needed:
            return {
                'success': False,
                'error': 'Insufficient credits for strategy fees',
                'available': balance,
                'recommended_minimum': min_credits_needed
            }

        # Create pending fees record with fee_percent
        self.db.execute_sql(
            """INSERT OR REPLACE INTO pending_strategy_fees
               (strategy_run_id, user_id, creator_user_id, fee_percent, accumulated_satoshis, trade_count)
               VALUES (?, ?, ?, ?, 0, 0)""",
            (strategy_run_id, user_id, creator_user_id, fee_percent)
        )

        logger.info(f"Started fee accumulation for run {strategy_run_id}: "
                   f"user={user_id}, creator={creator_user_id}, fee_percent={fee_percent}%")

        return {'success': True, 'strategy_run_id': strategy_run_id}

    def accumulate_trade_fee(self, strategy_run_id: str, exchange_fee_satoshis: int,
                             is_profitable: bool) -> dict:
        """
        Accumulate fee for a single trade (called after each trade).
        Only charges on profitable trades.

        Args:
            strategy_run_id: Strategy run identifier.
            exchange_fee_satoshis: Exchange commission in satoshis.
            is_profitable: Whether the trade was profitable.

        Returns:
            Dict with fee charged amount.
        """
        if not is_profitable:
            return {'success': True, 'fee_charged': 0, 'reason': 'unprofitable_trade'}

        # Get the fee_percent for this strategy run
        result = self.db.execute_sql(
            "SELECT fee_percent FROM pending_strategy_fees WHERE strategy_run_id = ?",
            (strategy_run_id,), fetch_one=True
        )

        if not result:
            return {'success': False, 'fee_charged': 0, 'reason': 'no_pending_fees_record'}

        fee_percent = result[0]

        # Fee is fee_percent% of exchange commission
        strategy_fee = (exchange_fee_satoshis * fee_percent) // 100

        if strategy_fee <= 0:
            return {'success': True, 'fee_charged': 0, 'reason': 'fee_too_small'}

        # Add to accumulated fees
        self.db.execute_sql(
            """UPDATE pending_strategy_fees
               SET accumulated_satoshis = accumulated_satoshis + ?,
                   trade_count = trade_count + 1
               WHERE strategy_run_id = ?""",
            (strategy_fee, strategy_run_id)
        )

        logger.debug(f"Accumulated {strategy_fee} sats fee ({fee_percent}% of {exchange_fee_satoshis}) for run {strategy_run_id}")

        return {'success': True, 'fee_charged': strategy_fee}

    def settle_accumulated_fees(self, strategy_run_id: str) -> dict:
        """
        Settle all accumulated fees when strategy stops.
        Transfers total from user to creator.

        Args:
            strategy_run_id: Strategy run identifier.

        Returns:
            Dict with settlement details.
        """
        # Get pending fees
        pending = self.db.execute_sql(
            """SELECT user_id, creator_user_id, accumulated_satoshis, trade_count
               FROM pending_strategy_fees WHERE strategy_run_id = ?""",
            (strategy_run_id,), fetch_one=True
        )

        if not pending:
            return {'success': True, 'message': 'No pending fees found', 'settled': 0}

        user_id, creator_user_id, total_fees, trade_count = pending

        if total_fees <= 0:
            # Clean up record, no fees to settle
            self.db.execute_sql(
                "DELETE FROM pending_strategy_fees WHERE strategy_run_id = ?",
                (strategy_run_id,)
            )
            return {'success': True, 'settled': 0, 'trades': trade_count}

        # Check user still has sufficient balance
        balance = self.get_credits_balance(user_id)
        if balance < total_fees:
            # Settle what we can, log the shortfall
            logger.warning(f"User {user_id} has insufficient balance for full fee settlement. "
                          f"Owed: {total_fees}, Available: {balance}")
            total_fees = max(0, balance)  # Settle partial

        if total_fees > 0:
            idempotency_key = f"fee_settle_{strategy_run_id}"

            # Check if already settled (idempotency)
            existing = self.db.execute_sql(
                "SELECT id FROM credits_ledger WHERE idempotency_key = ?",
                (idempotency_key,), fetch_one=True
            )
            if existing:
                logger.info(f"Fees already settled for run {strategy_run_id}")
            else:
                # Atomic fee settlement - both debit and credit in one transaction
                # Prevents partial state if one insert fails
                try:
                    self.db.execute_in_transaction([
                        # Debit user
                        ("""INSERT INTO credits_ledger
                           (user_id, amount_satoshis, tx_type, reference_id,
                            counterparty_user_id, idempotency_key)
                           VALUES (?, ?, 'fee_paid', ?, ?, ?)""",
                         (user_id, -total_fees, strategy_run_id, creator_user_id, idempotency_key)),
                        # Credit creator
                        ("""INSERT INTO credits_ledger
                           (user_id, amount_satoshis, tx_type, reference_id,
                            counterparty_user_id, idempotency_key)
                           VALUES (?, ?, 'fee_received', ?, ?, ?)""",
                         (creator_user_id, total_fees, strategy_run_id, user_id,
                          f"{idempotency_key}_creator"))
                    ])
                    logger.info(f"Settled {total_fees} sats from user {user_id} to creator {creator_user_id}")
                except Exception as e:
                    logger.error(f"Failed to settle fees for run {strategy_run_id}: {e}")
                    # Don't delete pending record - leave for retry
                    return {'success': False, 'error': f'Fee settlement failed: {e}'}

        # Clean up pending record
        self.db.execute_sql(
            "DELETE FROM pending_strategy_fees WHERE strategy_run_id = ?",
            (strategy_run_id,)
        )

        # Check balance cap after settlement
        self.check_balance_cap(user_id)

        return {'success': True, 'settled': total_fees, 'trades': trade_count}

    def get_pending_fees(self, strategy_run_id: str) -> dict:
        """
        Get current accumulated fees for a running strategy.

        Args:
            strategy_run_id: Strategy run identifier.

        Returns:
            Dict with accumulated fees and trade count.
        """
        result = self.db.execute_sql(
            """SELECT accumulated_satoshis, trade_count
               FROM pending_strategy_fees WHERE strategy_run_id = ?""",
            (strategy_run_id,), fetch_one=True
        )
        if not result:
            return {'accumulated_satoshis': 0, 'trade_count': 0}
        return {'accumulated_satoshis': result[0], 'trade_count': result[1]}

    def cancel_fee_accumulation(self, strategy_run_id: str) -> dict:
        """
        Cancel fee accumulation for a strategy run without charging fees.

        Used when strategy startup fails after a pending fee record has been created.

        Args:
            strategy_run_id: Strategy run identifier.

        Returns:
            Dict with cancellation status.
        """
        try:
            self.db.execute_sql(
                "DELETE FROM pending_strategy_fees WHERE strategy_run_id = ?",
                (strategy_run_id,)
            )
            return {'success': True}
        except Exception as e:
            logger.error(f"Failed to cancel fee accumulation for run {strategy_run_id}: {e}")
            return {'success': False, 'error': str(e)}

    # === Deposits and Withdrawals ===

    def credit_deposit(self, user_id: int, amount_satoshis: int,
                       network: str, tx_hash: str, vout: int) -> dict:
        """
        Credit user's ledger when BTC deposit is confirmed.

        Args:
            user_id: User ID to credit.
            amount_satoshis: Amount to credit.
            network: Bitcoin network ('testnet' or 'mainnet').
            tx_hash: Transaction hash.
            vout: Output index within transaction.

        Returns:
            Dict with success status.
        """
        idempotency_key = f"deposit_{network}_{tx_hash}_{vout}"

        existing = self.db.execute_sql(
            "SELECT id FROM credits_ledger WHERE idempotency_key = ?",
            (idempotency_key,), fetch_one=True
        )
        if existing:
            return {'success': True, 'already_processed': True}

        self.db.execute_sql(
            """INSERT INTO credits_ledger
               (user_id, amount_satoshis, tx_type, reference_id, idempotency_key)
               VALUES (?, ?, 'deposit', ?, ?)""",
            (user_id, amount_satoshis, tx_hash, idempotency_key)
        )

        logger.info(f"Credited {amount_satoshis} sats to user {user_id} from deposit {tx_hash}")

        # Check balance cap after deposit
        cap_check = self.check_balance_cap(user_id)
        if cap_check['over_cap']:
            logger.warning(f"User {user_id} wallet disabled - over balance cap after deposit")

        return {'success': True, 'over_cap': cap_check.get('over_cap', False)}

    def request_withdrawal(self, user_id: int, amount_satoshis: int,
                          destination_address: str) -> dict:
        """
        Queue a withdrawal request with immediate reservation.

        Args:
            user_id: User ID requesting withdrawal.
            amount_satoshis: Amount to withdraw.
            destination_address: Bitcoin address to send to.

        Returns:
            Dict with success status.
        """
        wallet = self.get_wallet(user_id)
        if not wallet:
            return {'success': False, 'error': 'No wallet'}

        # Validate address for the wallet's network
        bitcoin = BitcoinService(testnet=(wallet['network'] == 'testnet'))
        if not bitcoin.validate_address(destination_address):
            return {'success': False, 'error': 'Invalid Bitcoin address for this network'}

        balance = self.get_credits_balance(user_id)
        if balance < amount_satoshis:
            return {'success': False, 'error': 'Insufficient balance', 'available': balance}

        # Reserve immediately (debit from ledger)
        idempotency_key = f"withdrawal_reserve_{uuid.uuid4().hex}"
        try:
            self.db.execute_in_transaction([
                ("""INSERT INTO credits_ledger
                    (user_id, amount_satoshis, tx_type, reference_id, idempotency_key)
                    VALUES (?, ?, 'withdrawal', ?, ?)""",
                 (user_id, -amount_satoshis, destination_address, idempotency_key)),
                ("""INSERT INTO withdrawal_requests
                    (user_id, amount_satoshis, destination_address, status)
                    VALUES (?, ?, ?, 'reserved')""",
                 (user_id, amount_satoshis, destination_address))
            ])
        except Exception as e:
            logger.error(f"Failed to reserve withdrawal for user {user_id}: {e}")
            return {'success': False, 'error': 'Failed to queue withdrawal request'}

        logger.info(f"Withdrawal requested: {amount_satoshis} sats to {destination_address} for user {user_id}")

        # Re-check balance cap (may re-enable wallet after withdrawal)
        self.check_balance_cap(user_id)

        return {'success': True, 'message': 'Withdrawal reserved and queued'}

    def admin_credit(self, user_id: int, amount_satoshis: int, reason: str) -> dict:
        """
        Admin function to manually credit a user (for POC testing).

        Args:
            user_id: User ID to credit.
            amount_satoshis: Amount to credit.
            reason: Reason for the credit.

        Returns:
            Dict with success status.
        """
        idempotency_key = f"admin_{user_id}_{uuid.uuid4().hex}"
        self.db.execute_sql(
            """INSERT INTO credits_ledger
               (user_id, amount_satoshis, tx_type, reference_id, idempotency_key)
               VALUES (?, ?, 'admin_credit', ?, ?)""",
            (user_id, amount_satoshis, reason, idempotency_key)
        )

        logger.info(f"Admin credited {amount_satoshis} sats to user {user_id}: {reason}")

        self.check_balance_cap(user_id)
        return {'success': True}

    def get_transaction_history(self, user_id: int, limit: int = 20) -> list:
        """
        Get user's recent ledger transactions.

        Args:
            user_id: User ID to look up.
            limit: Maximum number of transactions to return.

        Returns:
            List of transaction dicts.
        """
        results = self.db.execute_sql(
            """SELECT tx_type, amount_satoshis, reference_id, created_at
               FROM credits_ledger WHERE user_id = ?
               ORDER BY created_at DESC LIMIT ?""",
            (user_id, limit), fetch_all=True
        )
        return [{'type': r[0], 'amount': r[1], 'reference': r[2], 'date': r[3]}
                for r in (results or [])]

    # === Background Job Support Methods ===

    def get_wallets_over_cap(self) -> list:
        """
        Get all wallets with balance exceeding the cap (for auto-sweep).

        Returns:
            List of dicts with user_id, balance, and sweep_address.
        """
        # Get all users with wallets and compute their balances
        results = self.db.execute_sql(
            """SELECT w.user_id, w.sweep_address,
                      COALESCE(SUM(cl.amount_satoshis), 0) as balance
               FROM wallets w
               LEFT JOIN credits_ledger cl ON w.user_id = cl.user_id
               WHERE w.sweep_address IS NOT NULL
               GROUP BY w.user_id
               HAVING balance > ?""",
            (BALANCE_CAP_SATOSHIS,), fetch_all=True
        )
        return [{'user_id': r[0], 'sweep_address': r[1], 'balance': r[2]}
                for r in (results or [])]

    def auto_sweep(self, user_id: int, amount_satoshis: int) -> dict:
        """
        Sweep excess funds to user's sweep address.

        Args:
            user_id: User ID to sweep for.
            amount_satoshis: Amount to sweep.

        Returns:
            Dict with success status and tx_hash.
        """
        wallet = self.get_wallet(user_id)
        if not wallet:
            return {'success': False, 'error': 'No wallet'}

        sweep_address = wallet.get('sweep_address')
        if not sweep_address:
            return {'success': False, 'error': 'No sweep address configured'}

        # Get fee address private key
        keys = self.get_wallet_keys(user_id)
        if not keys:
            return {'success': False, 'error': 'Could not retrieve wallet keys'}

        bitcoin = BitcoinService(testnet=(wallet['network'] == 'testnet'))

        try:
            # Execute the sweep transaction
            tx_hash = bitcoin.send_transaction(
                private_key_wif=keys['fee_private_key'],
                to_address=sweep_address,
                amount_satoshis=amount_satoshis
            )
            if not tx_hash:
                return {'success': False, 'error': 'Sweep transaction failed: no tx hash returned'}

            # Record the sweep as a withdrawal in the ledger
            idempotency_key = f"auto_sweep_{user_id}_{tx_hash}"
            self.db.execute_sql(
                """INSERT INTO credits_ledger
                   (user_id, amount_satoshis, tx_type, reference_id, idempotency_key)
                   VALUES (?, ?, 'withdrawal', ?, ?)""",
                (user_id, -amount_satoshis, f"auto_sweep:{tx_hash}", idempotency_key)
            )

            logger.info(f"Auto-swept {amount_satoshis} sats for user {user_id} to {sweep_address}: {tx_hash}")

            # Re-check balance cap
            self.check_balance_cap(user_id)

            return {'success': True, 'tx_hash': tx_hash}

        except Exception as e:
            logger.error(f"Auto-sweep failed for user {user_id}: {e}")
            return {'success': False, 'error': str(e)}

    def get_all_active_wallets(self) -> list:
        """
        Get all active (non-disabled) wallets for deposit checking.

        Returns:
            List of dicts with user_id, fee_address, and network.
        """
        results = self.db.execute_sql(
            """SELECT user_id, btc_address, network
               FROM wallets WHERE is_disabled = 0""",
            fetch_all=True
        )
        return [{'user_id': r[0], 'fee_address': r[1], 'network': r[2]}
                for r in (results or [])]

    def get_wallets_for_deposit_monitoring(self) -> list:
        """
        Get all wallets for deposit checking, including disabled wallets.

        Disabled wallets must still receive deposit credits so users can move
        back under the cap via sweeping/withdrawal flows.

        Returns:
            List of dicts with user_id, fee_address, and network.
        """
        results = self.db.execute_sql(
            """SELECT user_id, btc_address, network
               FROM wallets""",
            fetch_all=True
        )
        return [{'user_id': r[0], 'fee_address': r[1], 'network': r[2]}
                for r in (results or [])]

    def check_address_for_deposits(self, user_id: int, address: str, network: str) -> list:
        """
        Check an address for new deposits and credit the user's ledger.

        Args:
            user_id: User ID who owns the address.
            address: Bitcoin address to check.
            network: Bitcoin network ('testnet' or 'mainnet').

        Returns:
            List of new deposits found and credited.
        """
        bitcoin = BitcoinService(testnet=(network == 'testnet'))
        new_deposits = []

        try:
            # Get unspent outputs (UTXOs) for the address
            utxos = bitcoin.get_unspent(address)

            for utxo in utxos:
                tx_hash = utxo.txid
                vout = utxo.txindex
                amount_satoshis = utxo.amount  # bit library returns in satoshis

                # Check if we've already processed this deposit
                existing = self.db.execute_sql(
                    """SELECT id, credited FROM deposits
                       WHERE network = ? AND tx_hash = ? AND vout = ?""",
                    (network, tx_hash, vout), fetch_one=True
                )

                if existing and int(existing[1]) == 1:
                    continue  # Already processed

                # Ensure tracking row exists. Keep credited=0 until ledger credit succeeds.
                if not existing:
                    self.db.execute_sql(
                        """INSERT OR IGNORE INTO deposits
                           (user_id, network, tx_hash, vout, amount_satoshis, credited)
                           VALUES (?, ?, ?, ?, ?, 0)""",
                        (user_id, network, tx_hash, vout, amount_satoshis)
                    )

                # Credit the user's ledger
                result = self.credit_deposit(user_id, amount_satoshis, network, tx_hash, vout)

                if result.get('success'):
                    self.db.execute_sql(
                        """UPDATE deposits
                           SET credited = 1
                           WHERE network = ? AND tx_hash = ? AND vout = ?""",
                        (network, tx_hash, vout)
                    )

                    if not result.get('already_processed'):
                        new_deposits.append({
                            'tx_hash': tx_hash,
                            'vout': vout,
                            'amount_satoshis': amount_satoshis
                        })

        except Exception as e:
            logger.error(f"Error checking deposits for {address}: {e}")

        return new_deposits

    def get_pending_withdrawals(self) -> list:
        """
        Get all pending withdrawal requests (status = 'reserved').

        Returns:
            List of pending withdrawal dicts.
        """
        results = self.db.execute_sql(
            """SELECT id, user_id, amount_satoshis, destination_address
               FROM withdrawal_requests
               WHERE status = 'reserved'
               ORDER BY created_at ASC""",
            fetch_all=True
        )
        return [{'id': r[0], 'user_id': r[1], 'amount_satoshis': r[2], 'destination_address': r[3]}
                for r in (results or [])]

    def process_withdrawal(self, withdrawal_id: int) -> dict:
        """
        Process a pending withdrawal request.

        Args:
            withdrawal_id: ID of the withdrawal request.

        Returns:
            Dict with success status and tx_hash.
        """
        # Get withdrawal details
        result = self.db.execute_sql(
            """SELECT user_id, amount_satoshis, destination_address, status
               FROM withdrawal_requests WHERE id = ?""",
            (withdrawal_id,), fetch_one=True
        )

        if not result:
            return {'success': False, 'error': 'Withdrawal not found'}

        user_id, amount_satoshis, destination_address, status = result

        if status != 'reserved':
            return {'success': False, 'error': f'Invalid status: {status}'}

        # Get wallet and keys
        wallet = self.get_wallet(user_id)
        if not wallet:
            self._fail_withdrawal(withdrawal_id, 'No wallet found')
            return {'success': False, 'error': 'No wallet'}

        keys = self.get_wallet_keys(user_id)
        if not keys:
            self._fail_withdrawal(withdrawal_id, 'Could not retrieve wallet keys')
            return {'success': False, 'error': 'Could not retrieve keys'}

        # Mark as processing
        self.db.execute_sql(
            "UPDATE withdrawal_requests SET status = 'processing' WHERE id = ?",
            (withdrawal_id,)
        )

        bitcoin = BitcoinService(testnet=(wallet['network'] == 'testnet'))

        try:
            # Execute the withdrawal transaction
            tx_hash = bitcoin.send_transaction(
                private_key_wif=keys['fee_private_key'],
                to_address=destination_address,
                amount_satoshis=amount_satoshis
            )
            if not tx_hash:
                raise RuntimeError("Withdrawal transaction failed: no tx hash returned")

            # Mark as completed
            self.db.execute_sql(
                """UPDATE withdrawal_requests
                   SET status = 'completed', btc_txhash = ?, processed_at = CURRENT_TIMESTAMP
                   WHERE id = ?""",
                (tx_hash, withdrawal_id)
            )

            logger.info(f"Withdrawal {withdrawal_id} completed: {tx_hash}")

            return {'success': True, 'tx_hash': tx_hash}

        except Exception as e:
            logger.error(f"Withdrawal {withdrawal_id} failed: {e}")
            self._fail_withdrawal(withdrawal_id, str(e))
            return {'success': False, 'error': str(e)}

    def _fail_withdrawal(self, withdrawal_id: int, error_message: str):
        """
        Mark a withdrawal as failed and reverse the ledger debit.

        Args:
            withdrawal_id: ID of the failed withdrawal.
            error_message: Error message to record.
        """
        # Get withdrawal details
        result = self.db.execute_sql(
            """SELECT user_id, amount_satoshis, destination_address
               FROM withdrawal_requests WHERE id = ?""",
            (withdrawal_id,), fetch_one=True
        )

        if not result:
            return

        user_id, amount_satoshis, destination_address = result

        # Mark as failed
        self.db.execute_sql(
            """UPDATE withdrawal_requests
               SET status = 'failed', error_message = ?, processed_at = CURRENT_TIMESTAMP
               WHERE id = ?""",
            (error_message, withdrawal_id)
        )

        # Reverse the ledger debit (credit the user back)
        idempotency_key = f"withdrawal_reversal_{withdrawal_id}"

        # Check if already reversed
        existing = self.db.execute_sql(
            "SELECT id FROM credits_ledger WHERE idempotency_key = ?",
            (idempotency_key,), fetch_one=True
        )

        if not existing:
            self.db.execute_sql(
                """INSERT INTO credits_ledger
                   (user_id, amount_satoshis, tx_type, reference_id, idempotency_key)
                   VALUES (?, ?, 'withdrawal_reversal', ?, ?)""",
                (user_id, amount_satoshis, f"failed_withdrawal_{withdrawal_id}", idempotency_key)
            )
            logger.info(f"Reversed withdrawal {withdrawal_id} - credited {amount_satoshis} sats back to user {user_id}")
