/* Import modules. */
import moment from 'moment'
import PouchDB from 'pouchdb'

import { listUnspent } from '@nexajs/address'
import { sha256 } from '@nexajs/crypto'
import { encodePrivateKeyWif } from '@nexajs/hdnode'
import { sendCoin } from '@nexajs/purse'
import { encodeNullData } from '@nexajs/script'
import { hexToBin } from '@nexajs/utils'
import { Wallet } from '@nexajs/wallet'

/* Initialize databases. */
const playsDb = new PouchDB(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@127.0.0.1:5984/plays`)
const walletDb = new PouchDB(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@127.0.0.1:5984/wallet`)

/* Set constants. */
const DUST_LIMIT = BigInt(546)
const ESTIMATED_NUM_OUTPUTS = 5

const VAULT_MNEMONIC = process.env.MNEMONIC

/* Initialize wallet. */
const vaultWallet = new Wallet(VAULT_MNEMONIC)

/* Initialize spent coins. */
const spentCoins = []

/**
 * Handle (Wallet) Queue
 *
 * Process the pending queue of open transactions.
 *
 * NOTE: We handle payment processing in a SINGLE thread,
 *       to mitigate the possibility of a "double send".
 */
export default async (_queue, _pending) => {
    let latestDb
    let response
    let txResult
    let updatedDb
    let unspent
    let wif

    for (let i = 0; i < _pending.length; i++) {
        const payment = _queue[_pending[i]]
        console.log('PAYMENT (pending):', payment)
        console.log('PAYMENT (pending) receivers:', payment.receivers)

        /* Remove (payment) from queue. */
        delete _queue[_pending[i]]

        const paymentSatoshis = payment.receivers
            .reduce(
                (totalValue, receiver) => (totalValue + receiver.satoshis), BigInt(0)
            )
        // console.log('PAYMENT SATOSHIS', paymentSatoshis)

        const wallet = new Wallet(process.env.MNEMONIC)
        // console.log('WALLET', wallet)

        const address = wallet.address
        // console.log('TREASURY ADDRESS', address)

        /* Initialize receivers. */
        const receivers = []

        unspent = await listUnspent(address)
        // console.log('UNSPENT', unspent)

        /* Encode Private Key WIF. */
        wif = encodePrivateKeyWif(wallet.privateKey, 'mainnet')
        // console.log('PRIVATE KEY (WIF):', wif)

        /* Filter out ANY tokens. */
        // FIXME We should probably do something better than this, lol.
        // unspent = unspent.filter(_unspent => {
        //     return _unspent.satoshis > DUST_LIMIT
        // })
        /* Filter out ANY tokens & spent. */
        // FIXME We should probably do something better than this, lol.
        // unspent = unspent.filter(_unspent => {
        //     /* Initialize flag. */
        //     let isValid = true
        //
        //     if (_unspent.satoshis <= DUST_LIMIT) {
        //         /* Set flag. */
        //         isValid = false
        //     }
        //
        //     if (spentCoins.includes(_unspent.outpoint)) {
        //         /* Set flag. */
        //         isValid = false
        //     }
        //
        //     /* Return flag. */
        //     return isValid
        // })

        /* Validate unspent outputs. */
        if (unspent.length === 0) {
            return console.error('There are NO unspent outputs available.')
        }

        /* Build parameters. */
        const coins = unspent.map(_unspent => {
            const outpoint = _unspent.outpoint
            const satoshis = _unspent.satoshis

            return {
                outpoint,
                satoshis,
                wif,
            }
        })
        // console.log('\n  Coins-1:', coins)

        const wallet2 = new Wallet(payment.entropy)
        // console.log('WALLET-2', wallet2)

        const address2 = wallet2.address
        // console.log('TREASURY ADDRESS-2', address2)

        const pk2 = wallet2.privateKey
        // console.log('PRIVATE KEY-2', pk2)

        const playerCoin = {
            outpoint: payment.unspent.outpoint,
            satoshis: payment.unspent.satoshis,
            wif: encodePrivateKeyWif(pk2, 'mainnet'),
        }
        coins.unshift(playerCoin)
        // console.log('\n  Coins-2:', coins)

        /* Calculate the total balance of the unspent outputs. */
        const unspentSatoshis = coins
            .reduce(
                (totalValue, coin) => (totalValue + coin.satoshis), BigInt(0)
            )
        // console.log('UNSPENT SATOSHIS', unspentSatoshis)

        const userData = `NEXA.games~${payment.id}`
        // console.log('BLOCKCHAIN DATA', chainData)

        /* Initialize hex data. */
        const nullData = encodeNullData(userData)
        // console.log('HEX DATA', nullData)

        // NOTE: 150b (per input), 35b (per output), 10b (misc)
        // NOTE: Double the estimate (for safety).
        // const feeEstimate = ((coins.length * 150) + (35 * ESTIMATED_NUM_OUTPUTS) + 10 + (chainData.length / 2)) * 2
        // console.log('FEE ESTIMATE', feeEstimate)

        // TODO Validate data length is less than OP_RETURN max (220).

        /* Add OP_RETURN data. */
        receivers.push({
            data: nullData,
        })

        /* Add value output. */
        for (let j = 0; j < payment.receivers.length; j++) {
            if (payment.receivers[j].satoshis > DUST_LIMIT) {
                receivers.push({
                    address: payment.receivers[j].address,
                    satoshis: payment.receivers[j].satoshis,
                })
            }
        }

        /* Set change receiver. */
        receivers.push({
            address: vaultWallet.address
        })
        console.log('\n  Receivers:', receivers)

        /* Send UTXO request. */
        response = await sendCoin(coins, receivers)
        console.log('Send UTXO (response):', response)

        try {
            txResult = JSON.parse(response)
            console.log('TX RESULT', txResult)

            /* Validate transaction result. */
            if (txResult?.result) {
                /* Manage coins. */
                // coins.forEach(_coin => {
                //     /* Add hash to spent. */
                //     spentCoins.push(_coin.outpoint)
                //
                //     // TODO Add check for MAX spent (eg. 100).
                // })

                latestDb = await walletDb
                    .get(payment.id)
                    .catch(err => console.err(err))
                // console.log('LATEST (wallet)', latestDb)

                updatedDb = {
                    ...latestDb,
                    txidem: txResult?.result,
                    updatedAt: moment().valueOf(),
                }
                // console.log('UPDATED (wallet)', updatedDb)

                response = await walletDb
                    .put(updatedDb)
                    .catch(err => console.error(err))
                // console.log('UPDATE (wallet) RESPONSE', response)

                latestDb = await playsDb
                    .get(payment.id)
                    .catch(err => console.err(err))
                // console.log('LATEST (plays)', latestDb)

                updatedDb = {
                    ...latestDb,
                    txidem: txResult?.result,
                    updatedAt: moment().valueOf(),
                    updatedBy: 'WALLET',
                }
                // console.log('UPDATED (plays)', updatedDb)

                response = await playsDb
                    .put(updatedDb)
                    .catch(err => console.error(err))
                // console.log('UPDATE (plays) RESPONSE', response)
            }

            // TODO SEND EMAIL ERROR
            // txResult?.error
            // txResult?.result
        } catch (err) {
            console.error(err)
        }
    }
}
