#!/usr/bin/env python3
# Copyright (c) 2019 The Bitcoin Unlimited developers
"""
Tests to check if basic electrum server integration works
"""

import asyncio
from test_framework.util import (
    assert_equal,
    assert_raises_async,
    wait_for,
)
from test_framework.electrumutil import (
    ElectrumTestFramework,
)
from test_framework.constants import COIN
from test_framework.environment import on_bch, on_nex
from test_framework.serialize import to_hex
from test_framework.connectrum.exc import ElectrumErrorResponse
from test_framework.electrumconnection import ElectrumConnection

if on_bch():
    from test_framework.bch.nodemessages import CTransaction, CTxIn, COutPoint
elif on_nex():
    from test_framework.nex.nodemessages import CTransaction, CTxIn, COutPoint
else:
    raise NotImplementedError()


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

        self.info("Checking that blocks are indexed")
        n.generate(200)

        electrum_client = ElectrumConnection()
        try:
            await electrum_client.connect()
            await self.test_mempoolsync(n, electrum_client)
            await self.test_unknown_method(electrum_client)
            await self.test_invalid_args(electrum_client)
            await self.test_address_balance(n, electrum_client)
        finally:
            electrum_client.disconnect()

    async def test_mempoolsync(self, n, cli):
        # throws on timeout, failing the test
        await self.sync_all(cli, n)

        self.info("Check that mempool is communicated")
        n.sendtoaddress(n.getnewaddress(), 10)
        assert_equal(1, len(n.getrawtxpool()))
        await self.wait_for_mempool_count(cli, count=1)

        n.generate(1)
        await self.sync_height(cli)
        assert_equal(0, len(n.getrawtxpool()))
        await self.wait_for_mempool_count(cli, count=0)

    async def test_unknown_method(self, electrum_client):
        await assert_raises_async(
            ElectrumErrorResponse,
            electrum_client.call,
            "method.that.does.not.exist",
            42,
        )

        try:
            await electrum_client.call("method.that.does.not.exist")
        except ElectrumErrorResponse as e:
            error_code = "-32601"
            assert error_code in str(e)

    async def test_invalid_args(self, electrum_client):
        error_code = "-32602"

        hash_param_methods = (
            "blockchain.scripthash.get_balance",
            "blockchain.scripthash.get_history",
            "blockchain.scripthash.listunspent",
        )

        for method in hash_param_methods:
            await assert_raises_async(
                ElectrumErrorResponse, electrum_client.call, method, "invalidhash"
            )
            try:
                await electrum_client.call(method, "invalidhash")
            except ElectrumErrorResponse as e:
                print("ERROR:" + str(e))
                assert error_code in str(e)

        # invalid tx
        try:
            tx = CTransaction()
            if on_nex():
                tx.vin = [CTxIn(COutPoint().from_idem_and_idx(0xF00D, 0))]
            else:
                tx.vin = [CTxIn(COutPoint(0xF00D, 0))]
            tx.rehash()
            await electrum_client.call("blockchain.transaction.broadcast", to_hex(tx))
        except Exception as e:
            assert error_code in str(e), e

    async def test_address_balance(self, n, electrum_client):
        addr = n.getnewaddress()
        n.sendtoaddress(addr, 10)

        async def check_address(address, unconfirmed=0, confirmed=0):
            res = await electrum_client.call("blockchain.address.get_balance", address)
            print(res, "vs", unconfirmed * COIN)

            return (
                res["unconfirmed"] == unconfirmed * COIN
                and res["confirmed"] == confirmed * COIN
            )

        async def check_unconfirmed_balance():
            return await check_address(addr, unconfirmed=10)

        async def check_confirmed_balance():
            return await check_address(addr, confirmed=10)

        await wait_for(10, check_unconfirmed_balance)
        n.generate(1)
        await wait_for(10, check_confirmed_balance)


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