#!/usr/bin/env python3
# Copyright (c) 2022 The Bitcoin Unlimited developers
import asyncio
from test_framework.utilcompat import lock, unlock
from test_framework.electrumutil import script_to_scripthash
from test_framework.util import assert_equal, wait_for
from test_framework.electrumutil import (
    ElectrumTestFramework,
)
from test_framework.environment import on_bch, on_nex
from test_framework.script import (
    CScript,
    OP_DROP,
    OP_FALSE,
    OP_TRUE,
)
from test_framework.serialize import to_hex
from test_framework.utiltx import pad_tx
from test_framework.electrumconnection import ElectrumConnection

if on_bch():
    from test_framework.bch.blocktools import create_transaction
elif on_nex():
    from test_framework.nex.blocktools import create_transaction
else:
    raise NotImplementedError()

SCRIPT1 = CScript([OP_FALSE, OP_DROP])
SCRIPT2 = CScript([OP_TRUE, OP_DROP])
SCRIPT3 = CScript([OP_FALSE, OP_FALSE, OP_DROP, OP_DROP])
SCRIPT4 = CScript([OP_TRUE, OP_FALSE, OP_DROP, OP_DROP])


def new_tx(prevtx, lock_script, amount, unlock_script):
    tx = create_transaction(
        prevtx=prevtx, value=amount, n=0, sig=unlock_script, out=lock_script
    )
    pad_tx(tx)
    return tx


class BlockchainScripthashGetBalanceTests(ElectrumTestFramework):
    async def run_test(self):
        n = self.nodes[0]

        await self.bootstrap_p2p()
        cli = ElectrumConnection()
        await cli.connect()
        coinbases = await self.mine_blocks(cli, n, 104)
        try:
            await self.test_balance(n, cli, [coinbases.pop(0), coinbases.pop(0)])
            await self.test_zeroes_out(n, cli, coinbases.pop(0))
            await self.test_negative_balance(n, cli, coinbases.pop(0))

        finally:
            cli.disconnect()

    async def test_balance(self, n, cli, coinbases):
        """
        Test that balance shows up as confirmed, then unconfirmed.
        """
        tx1 = new_tx(coinbases.pop(0), lock(SCRIPT1), 10000, unlock(CScript([])))
        tx2 = new_tx(coinbases.pop(0), lock(SCRIPT1), 10000, unlock(CScript([])))
        n.sendrawtransaction(to_hex(tx1), True)
        n.sendrawtransaction(to_hex(tx2), True)
        await self.sync_mempool(cli, n)
        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT1))
        )
        assert_equal(0, b["confirmed"])
        assert_equal(20000, b["unconfirmed"])
        await self.mine_blocks(cli, n, 1, [tx1, tx2])
        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT1))
        )
        assert_equal(20000, b["confirmed"])
        assert_equal(0, b["unconfirmed"])

    async def test_zeroes_out(self, n, cli, coinbase):
        """
        Special case. Sending and receiving the same
        amount results in unconfirmed balance of 0.

        This applies to both mempool and confirmed, if as
        long as the funding tx and spending tx is in the same
        index.
        """

        # Mempool
        fee = 500
        tx1 = new_tx(coinbase, lock(SCRIPT4), 10000, unlock(CScript([])))
        tx1.rehash()
        tx2 = new_tx(tx1, lock(SCRIPT2), 10000 - fee, unlock(SCRIPT4))
        tx2.rehash()
        n.sendrawtransaction(to_hex(tx1), True)
        n.sendrawtransaction(to_hex(tx2), True)
        await self.wait_for_mempool_tx(cli, tx1.get_rpc_hex_id())
        await self.wait_for_mempool_tx(cli, tx2.get_rpc_hex_id())
        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT4))
        )
        await wait_for(10, lambda: 0 == b["confirmed"])
        await wait_for(10, lambda: 0 == b["unconfirmed"])

        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT2))
        )
        # race condition in CI?
        await wait_for(5, lambda: b["unconfirmed"] == 10000 - fee)
        # assert_equal(10000 - fee, b["unconfirmed"])

        # Confirmed index
        await self.mine_blocks(cli, n, 1, [tx1, tx2])
        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT4))
        )
        assert_equal(0, b["confirmed"])

        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT2))
        )
        assert_equal(10000 - fee, b["confirmed"])

    async def test_negative_balance(self, n, cli, coinbase):
        """
        Spending coins tokens results in negative mempool
        balance.
        """
        fee = 500
        tx1 = new_tx(coinbase, lock(SCRIPT3), 10000, unlock(CScript([])))
        await self.mine_blocks(cli, n, 1, [tx1])

        # Spend from SCRIPT3
        tx2 = new_tx(tx1, lock(SCRIPT2), 10000 - fee, unlock(SCRIPT3))
        n.sendrawtransaction(to_hex(tx2), True)
        await self.sync_mempool(cli, n)

        b = await cli.call(
            "blockchain.scripthash.get_balance", script_to_scripthash(lock(SCRIPT3))
        )
        assert_equal(-10000, b["unconfirmed"])
        assert_equal(10000, b["confirmed"])


if __name__ == "__main__":
    asyncio.run(BlockchainScripthashGetBalanceTests().main())
