#!/usr/bin/env python3
# Copyright (c) 2016-2025 The Hush developers
# Distributed under the GPLv3 software license, see the accompanying
# file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html

from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, assert_greater_than, assert_greater_than_or_equal, \
    initialize_chain_clean, initialize_chain, start_nodes, start_node, connect_nodes_bi, \
    stop_nodes, sync_blocks, sync_mempools, wait_bitcoinds, rpc_port, assert_raises, assert_true, \
    wait_and_assert_operationid_status

import time
from decimal import Decimal

def assert_success(result):
    assert_equal(result['result'], 'success')

def assert_error(result):
    assert_equal(result['result'], 'error')

class ShieldCoinbaseDonationTest (BitcoinTestFramework):
    def setup_chain(self):
        print("Initializing test directory "+self.options.tmpdir)
        self.num_nodes = 1
        self.options.nocleanup = 1 # do not delete datadir after test run
        #self.options.nocleanup = 0
        initialize_chain_clean(self.options.tmpdir, self.num_nodes)

    def setup_network(self, split = False):
        print("Setting up network...")
        self.supply = 555
        self.nodes   = start_nodes(self.num_nodes, self.options.tmpdir,
                    extra_args=[[
                    # always give -ac_name as first extra_arg and port as third
                    '-ac_name=ZZZ',
                    #'-ac_algo=randomx',
                    #'-testnode=1', # why does this make the test node hang before run_test() ?
                    '-conf='+self.options.tmpdir+'/node0/regtest/ZZZ.conf',
                    '-port=63367',
                    '-rpcport=63368',
                    '-ac_supply=' + str(self.supply),
                    '-ac_reward=300000000',
                    '-ac_private=1',
                    '-allowlist=127.0.0.1',
                    '-regtest',
                    '--daemon',
                    #'-debug',
                    '-zrpc',
                    '-zdebug',
                    '-zrpcunsafe'
                    ]]
        )
        self.is_network_split = False
        self.rpc              = self.nodes[0]
        self.sync_all()
        print("Done setting up network")

    def run_test (self):
        # NOTE: order of these tests is important
        self.run_test_default()
        self.run_test_custom()
        self.run_test_custom_nondefault_fee()

    def run_test_default(self):
        rpc    = self.nodes[0]

        # mine initial ac_supply
        rpc.generate(1)
        self.sync_all()

        zaddr1 = rpc.z_getnewaddress()
        #rpc.z_exportkey(zaddr1)

        # first we test the default situation where no donation is given and
        # it defaults to 0
        response = rpc.z_shieldcoinbase('*', zaddr1, 0, 1)
        opid = response['opid']
        shieldingValue = response['shieldingValue']

        assert_true( shieldingValue >=  self.supply )

        print("opid=" + opid)

        time.sleep(2) # give some time for the ztx to complete

        # 555 supply plus magic utxo = 555.05537304
        # NOTE: if any consensus params for this testcoin are changed above,
        # the magic utxo will change and this value will need to be updated
        totalSupply    = 55505537304 # in puposhis
        expectedAmount = totalSupply

        json = rpc.z_getoperationstatus()
        txid = json[0]['result']['txid']

        wait_and_assert_operationid_status(rpc, opid)

        rawtx0 = rpc.z_viewtransaction(txid)
        assert_equal( rawtx0['outputs'][0]['valueZat'] , expectedAmount, '5% donation sends correct sendAmount')

    def run_test_custom_nondefault_fee(self):
        rpc    = self.nodes[0]
        zaddr1 = rpc.z_getnewaddress()

        donation = 5

        # donation zaddr is already imported from previous test
        # NOTE: goal here is to test a situation where
        # sendAmount/donationAmount arithmetic leads to a situation where the
        # exact amount in satoshis must deal with truncation/rounding

        # shield funds to a new zaddr in this wallet with non-default fee
        fee = 0.00000001 # 1 puposhi fee will lead to some kind of rounding/truncation arithmetic
        response = rpc.z_shieldcoinbase('*', zaddr1, fee, 1, donation)
        opid = response['opid']
        print("opid=" + opid)

        shieldingValue = response['shieldingValue']
        # sanity check. None of the expected values below will be correct if
        # this is different
        assert_equal( str(shieldingValue) ,  "3.00010000" )

        # TODO: this might not be enough time for slow machines, better
        # solution would be to wait until the opid finishes
        time.sleep(2) # give some time for the ztx to complete

        # confirm tx from above
        rpc.generate(1)
        self.sync_all()

        # get the txid
        json = rpc.z_getoperationstatus()
        # NOTE: this is index 1 because this test runs after the above test
        # It would be better to specifically find the data for our opid
        txid = json[1]['result']['txid']
        print("txid=" + txid)

        rpc.z_listunspent()

        # (300010000 - 1)*.05
        # 15000499.95
        # (300010000 - 1) - 15000499
        # 285009500
        # The above value will be truncated (not rounded) by casting from
        # double to CAmount/int64_t which means the donation will be 15000499
        # and the sendAmount will be 300010000 - 1 (the fee) - 15000499 = 285009500


        # these values assume that 3.0001 was shielded
        expectedSendAmount     = 285009500
        expectedDonationAmount = 15000499

        # lookup txid
        rawtx1 = rpc.z_viewtransaction(txid)

        # TODO: set this up once for all tests since they all use the same zaddr
        donation_zaddr   = "zregtestsapling1y30nwg0clsu6gcyrnvht8hdyfk3vwtszlh6kc4z5hv9hmpxzg2g0nx7c60xeecggm9x9gma96t4"

        # there should be two outputs to different addresses, order is nondeterministic
        if rawtx1['outputs'][0]['address'] == donation_zaddr:
            donation_zout = 0
            other_zout    = 1
        else:
            donation_zout = 1
            other_zout    = 0

        assert_equal( rawtx1['outputs'][donation_zout]['address'] , donation_zaddr, 'correct zaddr gets donation')
        assert_equal( rawtx1['outputs'][donation_zout]['valueZat'] , expectedDonationAmount, '5% donation sends correct donationAmount')

        assert_equal( rawtx1['outputs'][other_zout]['address'] , zaddr1, 'correct zaddr gets main amount')
        assert_equal( rawtx1['outputs'][other_zout]['valueZat'] , expectedSendAmount, '5% donation sends correct sendAmount')

        #TODO: assert sum = 3

    def run_test_custom(self):
        rpc    = self.nodes[0]
        zaddr1  = rpc.z_getnewaddress()

        # generate some new coinbase funds
        rpc.generate(1)
        self.sync_all()

        testing_zaddr   = "zregtestsapling1y30nwg0clsu6gcyrnvht8hdyfk3vwtszlh6kc4z5hv9hmpxzg2g0nx7c60xeecggm9x9gma96t4"
        testing_privkey = "secret-extended-key-regtest1q0hgrms7qqqqpqrsz6myrtnh3ccp8uzp0kgxj6029wr6vq5hqvyccdlz7a745pgm5eeaamxqp9rxll2xctfrlw2l8xhxsc7zsut2tyz0374rrlk8srjswx7rhm6hcf2d7fuwajazvjesafduzxyka4w02tqjxdehzvghyrsd2zll90k3g2ckdvc5kqd6r7r7nglrtj0ej5a40d6lh8zxrvdlxrpuc59y5m8n9tekdxh4wpqn3smv5nxu4vvu58f8dgwn92qfqrvxqlscchtyh"

        # import zaddr that receives donation , no rescan
        rpc.z_importkey(testing_privkey, "no")

        rpc.z_listaddresses()

        # now we test giving a donation parameter
        donation = 5

        # shield funds to a new zaddr in this wallet with default fee
        fee = 0.0001
        response = rpc.z_shieldcoinbase('*', zaddr1, fee, 1, donation)
        opid = response['opid']
        print("opid=" + opid)
        #wait_and_assert_operationid_status(rpc, opid)
        shieldingValue = response['shieldingValue']

        assert_greater_than_or_equal( shieldingValue ,  3.0 )

        time.sleep(2) # give some time for the ztx to complete

        rpc.getinfo()

        rpc.generate(1)
        self.sync_all()

        # get the txid
        json = rpc.z_getoperationstatus()
        txid = json[0]['result']['txid']
        print("txid=" + txid)

        rpc.z_listunspent()

        # (3 - fee)*0.05
        expectedAmount1 = 14999500

        # lookup txid
        rawtx1 = rpc.z_viewtransaction(txid)
        # there should be two outputs to different addresses, order is nondeterministic
        if rawtx1['outputs'][0]['address'] == testing_zaddr:
            donation_zout = 0
            other_zout    = 1
        else:
            donation_zout = 1
            other_zout    = 0

        # print("donation_zout=%d other_zout=%d" % (donation_zout,other_zout) )

        assert_equal( rawtx1['outputs'][donation_zout]['address'] , testing_zaddr, 'correct zaddr gets donation')
        assert_equal( rawtx1['outputs'][donation_zout]['valueZat'] , expectedAmount1, '5% donation sends correct donationAmount')

        # (3 - fee)*0.95
        expectedAmount2 = 284990500
        assert_equal( rawtx1['outputs'][other_zout]['address'] , zaddr1, 'correct zaddr gets main amount')
        assert_equal( rawtx1['outputs'][other_zout]['valueZat'] , expectedAmount2, '5% donation sends correct sendAmount')


        assert_equal( expectedAmount1 + expectedAmount2, 299990000, 'sendAmount+donationAmount = targetAmount - fee' )


if __name__ == '__main__':
    ShieldCoinbaseDonationTest().main()

