package org.qortal.test.crosschain;

import cash.z.wallet.sdk.rpc.CompactFormats.*;
import com.google.common.hash.HashCode;
import com.google.common.primitives.Bytes;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.store.BlockStoreException;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.qortal.controller.tradebot.TradeBot;
import org.qortal.crosschain.*;
import org.qortal.crypto.Crypto;
import org.qortal.repository.DataException;
import org.qortal.test.common.Common;
import org.qortal.transform.TransformationException;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;

public class PirateChainTests extends Common {

	private PirateChain pirateChain;

	@Before
	public void beforeTest() throws DataException {
		Common.useDefaultSettings();
		pirateChain = PirateChain.getInstance();
	}

	@After
	public void afterTest() {
		Litecoin.resetForTesting();
		pirateChain = null;
	}

	@Test
	public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
		long before = System.currentTimeMillis();
		System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
		long afterFirst = System.currentTimeMillis();

		System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
		long afterSecond = System.currentTimeMillis();

		long firstPeriod = afterFirst - before;
		long secondPeriod = afterSecond - afterFirst;

		System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));

		assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L);
		assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
	}

	@Test
	public void testGetCompactBlocks() throws ForeignBlockchainException {
		int startHeight = 1000000;
		int count = 20;

		long before = System.currentTimeMillis();
		List<CompactBlock> compactBlocks = pirateChain.getCompactBlocks(startHeight, count);
		long after = System.currentTimeMillis();

		System.out.println(String.format("Retrieval took: %d ms", after-before));

		for (CompactBlock block : compactBlocks) {
			System.out.println(String.format("Block height: %d, transaction count: %d", block.getHeight(), block.getVtxCount()));
		}

		assertEquals(count, compactBlocks.size());
	}

	@Test
	public void testGetRawTransaction() throws ForeignBlockchainException {
		String txHashLE = "fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0";
		byte[] txBytes = HashCode.fromString(txHashLE).asBytes();
		// Pirate protocol expects txids in big-endian form, but block explorers use txids in little-endian form
		Bytes.reverse(txBytes);
		String txHashBE = HashCode.fromBytes(txBytes).toString();

		byte[] rawTransaction = pirateChain.getBlockchainProvider().getRawTransaction(txHashBE);
		assertNotNull(rawTransaction);
	}

	@Test
	public void testDeriveP2SHAddressWithT3Prefix() {
		byte[] creatorTradePrivateKey = TradeBot.generateTradePrivateKey();
		byte[] creatorTradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(creatorTradePrivateKey);
		byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
		byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
		byte[] secretA = TradeBot.generateSecret();
		byte[] hashOfSecretA = Crypto.hash160(secretA);
		int lockTime = 1653233550;

		byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTime, creatorTradeForeignPublicKey, hashOfSecretA);
		String p2shAddress = PirateChain.getInstance().deriveP2shAddress(redeemScriptBytes);
		assertTrue(p2shAddress.startsWith("t3"));
	}

	@Test
	public void testDeriveP2SHAddressWithBPrefix() {
		byte[] creatorTradePrivateKey = TradeBot.generateTradePrivateKey();
		byte[] creatorTradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(creatorTradePrivateKey);
		byte[] tradePrivateKey = TradeBot.generateTradePrivateKey();
		byte[] tradeForeignPublicKey = TradeBot.deriveTradeForeignPublicKey(tradePrivateKey);
		byte[] secretA = TradeBot.generateSecret();
		byte[] hashOfSecretA = Crypto.hash160(secretA);
		int lockTime = 1653233550;

		byte[] redeemScriptBytes = PirateChainHTLC.buildScript(tradeForeignPublicKey, lockTime, creatorTradeForeignPublicKey, hashOfSecretA);
		String p2shAddress = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptBytes);
		assertTrue(p2shAddress.startsWith("b"));
	}

	@Test
	public void testHTLCStatusFunded() throws ForeignBlockchainException {
		String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
		long p2shFee = 10000;
		final long minimumAmount = 10000 + p2shFee;
		BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
		assertEquals(FUNDED, htlcStatus);
	}

	@Test
	public void testHTLCStatusRedeemed() throws ForeignBlockchainException {
		String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka";
		long p2shFee = 10000;
		final long minimumAmount = 10000 + p2shFee;
		BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
		assertEquals(REDEEMED, htlcStatus);
	}

	@Test
	public void testHTLCStatusRefunded() throws ForeignBlockchainException {
		String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv";
		long p2shFee = 10000;
		final long minimumAmount = 10000 + p2shFee;
		BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
		assertEquals(REFUNDED, htlcStatus);
	}

	@Test
	public void testGetTxidForUnspentAddress() throws ForeignBlockchainException {
		String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
		String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress);

		// Reverse the byte order of the txid used by block explorers, to get to big-endian form
		byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
		Bytes.reverse(expectedTxidLE);
		String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString();

		assertEquals(expectedTxidBE, txid);
	}

	@Test
	public void testGetTxidForUnspentAddressWithMinimumAmount() throws ForeignBlockchainException {
		String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
		long p2shFee = 10000;
		final long minimumAmount = 10000 + p2shFee;
		String txid = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);

		// Reverse the byte order of the txid used by block explorers, to get to big-endian form
		byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
		Bytes.reverse(expectedTxidLE);
		String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString();

		assertEquals(expectedTxidBE, txid);
	}

	@Test
	public void testGetTxidForSpentAddress() throws ForeignBlockchainException {
		String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
		String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress);

		// Reverse the byte order of the txid used by block explorers, to get to big-endian form
		byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes();
		Bytes.reverse(expectedTxidLE);
		String expectedTxidBE = HashCode.fromBytes(expectedTxidLE).toString();

		assertEquals(expectedTxidBE, txid);
	}

	@Test
	public void testGetTransactionsForAddress() throws ForeignBlockchainException {
		String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
		List<BitcoinyTransaction> transactions = pirateChain.getBlockchainProvider()
				.getAddressBitcoinyTransactions(p2shAddress, false);

		assertEquals(2, transactions.size());
	}

	@Test
	public void testDecodeRawP2SHTransaction() throws TransformationException {
		String transactionDataHex = " ";
		BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex);
		assertEquals(0, bitcoinyTransaction.inputs.size());
		assertEquals(2, bitcoinyTransaction.outputs.size());
		assertEquals("a9140e8a360d8a54e3d684b7b43c293c6b26ca594abf87", bitcoinyTransaction.outputs.get(0).scriptPubKey);
		assertEquals("6a4c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac68", bitcoinyTransaction.outputs.get(1).scriptPubKey);
		assertEquals(0, bitcoinyTransaction.locktime);
	}

	@Test
	public void testDecodeRawRedeemTransaction() throws TransformationException {
		String transactionDataHex = "0400008085202f8901efb7c5be4e870464795737109ed02b6c9b5e60e8676f68245c390b4cab09917e00000000d847304402203929739d51dbc4b4d3435cf8b4930101601f670b3e8bcb23fde122b870385fba0220195aed5ed09b7dd342906c67c8da2896d00049bc75127c96724d03b7981fdbd80120baa6e68ffb2ddf19df6fce1b37d7e38b98f3706d5709e46d4f45f71c2e4bb7c301004c6b630436798662b17521037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac6782012088a91429096c1c7a55cc968a98032fbff1cf5f3e98b1c68821037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac68ffffffff0000000000a91e1d00f0d8ffffffffffff000116b2c57a7bcb22e6240ddfd5764ba12bbe589748c8cab6d34d40b2699457a3cbf7bdf6ada43d7649508ecc22f6f6d54d693b6b7434138f8aced6386be251803cd1fb65e7eb2a876ee1d41d74939d398dcb9152e755587325d51ff94b548baf4cff7db34f02e1e419294d8b5ddd55996179da0096751585213b87211626cbf4e4fde3c62a7b40310b375101cbd15983b2b9034fa29293cd1733ee36ade06027005f3e41f93394f15918663b82165aad714fa5851b41f981c77dfdeecdb779472ca30979ec888d33c30fae0d702a2b5fb8a349b5c466338ecc8eef63cb76d04b2ff6ef3b83029f06f875ce0f825fad3538fa5aa3b9db54eaa4f81533ba68af227552707e82e8ad8331b8bb4ba3624a087c4b2ddbe08f21771a0eab22491e444ad663afbe53ec25d2bd9e75295e78b4e9642e65ba04ea4f307a7d3062c4ccf85a71251147abcdd28f7c900b5e8ff19a7d9e24846e9477e277db46f8510a42f8e4a966783818ee8c9fcfa4dc2b1cc7c97b69f8bbd32b5249411b3f0308626e7af28e44a06031e673340c092ec0af3c3b9250c7f00da03ba9634fc0b1a05833330cc1c1a1915ead0a81d62ec420b83936aeff8835100060b2d7e22f9bdb4c679ec38bc3946d66d139c721093902031b0657524736316927588f70817bd117bd757cb64f5cea95d21a5c629e6e784e5f1ea7afce150a4c2e07ab35cca2d8d242de12fc953f2642ffb13fd3e03e25bb7a3993d853263c4a565694554452ae28bf8cb1d83793c3fc6a78a7302b0941c233ba9da1febd5185527727ab3b53aff475408e41924b490f99e532cb24295e1053160077c41fb892695ced2cc73d6bbb18bbd4d0b4d48a3d2c3500c5bb472f404617d00704d0407e51b52284d82e03b901f3acaf99990645ac2ee3a895cc3521126385e3046152666c132c902ba34db629c076396dd6aab718b97bd453d6b7dd3c080528fef4e0482413d448b7f83d1966e10f0fe0fcc5315498abceb84a9e3a6b7ca2e3d0694a92a71c4f1b32fb950acaecbaa72aec501f0dfecb87684b40bbb5abdbc0ae21db978c9112424201c8b711c776c008b87753e9f512be610d594fde628b17bbdbc7dd0c06b479c991d8d7826231f6f0d13529b0ac4f9ae4203fe7fda84c290b120f38e0c0b0c427846a6c3beb7216100a049ba9f014b4fcd88a5a08866ca1e1f3f09f118a77a280486d50241260829de93e8fcd039e0a274767d4388b900e4a2ce4b592f0e416997d7660df0555dee05ddbd484d9f1512bffb683f2d01966b123140bf72c5631e81b73a1f857547ed736c3b2a1f1ac12eddb8d7c9855b523988b72ed0052789154fe244eec1166e557c546f590c0ecc6d8a6988f1f4bb09ba2347cafd2dc1c779c08bb132ddd56d54a339ef6b9dc0f0217fbeed4dcb721394290051e06";
		BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex);
		assertEquals(1, bitcoinyTransaction.inputs.size());
		assertEquals(0, bitcoinyTransaction.outputs.size());
		assertEquals("47304402203929739d51dbc4b4d3435cf8b4930101601f670b3e8bcb23fde122b870385fba0220195aed5ed09b7dd342906c67c8da2896d00049bc75127c96724d03b7981fdbd80120baa6e68ffb2ddf19df6fce1b37d7e38b98f3706d5709e46d4f45f71c2e4bb7c301004c6b630436798662b17521037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac6782012088a91429096c1c7a55cc968a98032fbff1cf5f3e98b1c68821037f7e5ab23099885d373da9b9af701cffe06e62225f96f74be3ad6b5aeaad5b82ac68", bitcoinyTransaction.inputs.get(0).scriptSig);
		assertEquals("efb7c5be4e870464795737109ed02b6c9b5e60e8676f68245c390b4cab09917e", bitcoinyTransaction.inputs.get(0).outputTxHash);
		assertEquals(0, bitcoinyTransaction.inputs.get(0).outputVout);
		assertEquals(-1, bitcoinyTransaction.inputs.get(0).sequence);
		assertEquals(0, bitcoinyTransaction.locktime);
	}

	@Test
	public void testDecodeRawRefundTransaction() throws TransformationException {
		String transactionDataHex = "0400008085202f8901a8eca1f787163374a2628d2db3521644392cb926770437eaf3fba0eec86f38fb00000000b8483045022100eb217ddf7c671f4d9d74f02b5d5a4340ec69e7e433d6fb839dc1aa0bf8a5059b02206b45f78df09a75ad151bb4bc91e5d555098ceace53cda938bb52ff0ad34dd4a40101514c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac68feffffff0000738762111f1d00f0d8ffffffffffff00012a22d50c89ad18ec56b09efb1ce5044c7ad9abcd378f935189168b96210b1e407ede37181c3b51841a78e786a3ba1ef87baac7d7408205a13ec95447e3409c5819f6bbfd31a80ba5d41d367410c5b106788b9e75c49c2916a755b324a10e9210579f97aa8267a490e5e80210e123e657007bf0810294bcc8d73f7cf1b8a1acf2d87edf5dfff0e61423a48fffac84c648bfebec3ff0fa97009ff3d945c6e895b7f5a1e09d50a7920b776b28f5e97dac3eeed25ec0f1ad3cd3ac9e0517fe3c03990fa5303e75b60522e8ffc3ef3493d4460fa14cd8019a96416519408806ee2afa891f1335b6dfd4d1590e9f7939b0718af4758f832d836b928bdfc9329741d906e935316e3d4ec3a24eaf4fc06c321b78bd524d09b4fe09242dd7280ff3f841b56375a6cffd9661db089d1ee6ba4f6e767da194bd4fcd21e860bfa84dd7f431381795ff5998c66b594cf4bfb78c80d044f02e26d9ec0b35e5968ffb98c9be2b196a8a9238116cbd343b3718025c43ae65bacbbc400fd65f957aee17ffb7322aa1dca016a30a5bf21bac910a509eecd54274113c0f24a34786dda9b364e71d1ae0872db8ba06c864cf02bffec15c62ac240de0507230f88679cb7b7c5555c08277840679f89a8ebc2ccc37cdaee5486857293eb2a227191193e03d073ad6a6293a400dabf1ef90215585b78efb6f970490a07382072a0000666b6b70cde068e8841ee4b695088f7a136b44947d03f6961e88d88f79e5e6d7e0b96b61b3d4cc07cd4666fc1e5385915083e0af96ca937320babe0ba43d7914ee501a021fcb6ac02a2232f976a9b123f54ceb1524fe718aa143c1f509bc297419b6cb5277cf16e105a857fd38ce33bc437b52628a8c5eb0f6a4022901f9efddd26c325aa2ad393dd3ec11a75be2884927a322207e7ff8bacb5cdf78503e2051ddac6d2a990471bb9b95e0e865b491d17ec9ee245f8800476f9c4d2475f0ee01561f29edd64dff3d0af2c5847dfcbd498aae3a4c6d497ff9cb359b326599928643567634ef68d3483d81ba364c47e2418c94aa6b70a8cf451f67ba78a194de9e297e1c9be62f2d580e1c3ecd301a3875fcd63fdbf8bb17d0d815c893590fabd76b0bd0f96ab2ff4e43aee4df2f8b07e8cd7342632b9c263dd57df083b96a95276f7576005f840b0d3d95f55f82425381ebaa6cdd64d943c1387695be4206ac749621514d30e460d0260f32c9493d3c35ab99ab5e166fc2b187600996406f96eb0d6c634a683138fcfa31fd028db05233d09c0cdd02a7737e70ebc2c5c5a42c0e9c7ab030020623e8c9d4807dbf8dde6224ca2f75c09aa08d3147d9761c00fa7012218ef325383d64ad35a61e37dc34aae5de1a233ae607c11094508eb39f4813025c4bd121492842b69182c288ec481d4e68ed9ce160e2b4f682e2ff6806";
		BitcoinyTransaction bitcoinyTransaction = PirateChain.deserializeRawTransaction(transactionDataHex);
		assertEquals(1, bitcoinyTransaction.inputs.size());
		assertEquals(0, bitcoinyTransaction.outputs.size());
		assertEquals("483045022100eb217ddf7c671f4d9d74f02b5d5a4340ec69e7e433d6fb839dc1aa0bf8a5059b02206b45f78df09a75ad151bb4bc91e5d555098ceace53cda938bb52ff0ad34dd4a40101514c6b630400738762b17521029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac6782012088a914ab08c6bf2771e0b287303cc14cc02a6bd41a1fad8821029ddc2860644ef2a3b6c7310432e2f4231f21171e26f04150363340cd17565ac8ac68", bitcoinyTransaction.inputs.get(0).scriptSig);
		assertEquals("a8eca1f787163374a2628d2db3521644392cb926770437eaf3fba0eec86f38fb", bitcoinyTransaction.inputs.get(0).outputTxHash);
		assertEquals(0, bitcoinyTransaction.inputs.get(0).outputVout);
		assertEquals(-2, bitcoinyTransaction.inputs.get(0).sequence);
		assertEquals(1653043968, bitcoinyTransaction.locktime);
	}

	@Test
	@Ignore(value = "Doesn't work, to be fixed later")
	public void testFindHtlcSecret() throws ForeignBlockchainException {
		// This actually exists on TEST3 but can take a while to fetch
		String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";

		byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
		byte[] secret = BitcoinyHTLC.findHtlcSecret(pirateChain, p2shAddress);

		assertNotNull("secret not found", secret);
		assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
	}

	@Test
	@Ignore(value = "Needs adapting for Pirate Chain")
	public void testBuildSpend() {
		String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";

		String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
		long amount = 1000L;

		Transaction transaction = pirateChain.buildSpend(xprv58, recipient, amount);
		assertNotNull("insufficient funds", transaction);

		// Check spent key caching doesn't affect outcome

		transaction = pirateChain.buildSpend(xprv58, recipient, amount);
		assertNotNull("insufficient funds", transaction);
	}

	@Test
	@Ignore(value = "Needs adapting for Pirate Chain")
	public void testGetWalletBalance() throws ForeignBlockchainException {
		String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";

		Long balance = pirateChain.getWalletBalance(xprv58);

		assertNotNull(balance);

		System.out.println(pirateChain.format(balance));

		// Check spent key caching doesn't affect outcome

		Long repeatBalance = pirateChain.getWalletBalance(xprv58);

		assertNotNull(repeatBalance);

		System.out.println(pirateChain.format(repeatBalance));

		assertEquals(balance, repeatBalance);
	}

	@Test
	@Ignore(value = "Needs adapting for Pirate Chain")
	public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
		String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";

		String address = pirateChain.getUnusedReceiveAddress(xprv58);

		assertNotNull(address);

		System.out.println(address);
	}

}
