package org.smartwallet.stratum;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.google.common.collect.Lists;
import org.bitcoinj.core.*;
import org.junit.Before;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.concurrent.ExecutionException;

import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;

/**
 * Created by devrandom on 2015-Nov-23.
 */
public class StratumChainTest {
    private long nonce = 0;
    private StratumChain chain;
    private StratumClient client;
    private NetworkParameters params;
    private HeadersStore store;

    @Before
    public void setUp() throws IOException {
        File file = File.createTempFile("stratum-chain", ".chain");
        client = createMock(StratumClient.class);
        expect(client.getHeadersQueue()).andStubReturn(null);
        params = NetworkParameters.fromID(NetworkParameters.ID_UNITTESTNET);
        //new CheckpointManager(params, new ByteArrayInputStream("TXT CHECKPOINTS 1\n0\n0\n".getBytes("UTF-8")))
        store = new HeadersStore(params, file, null, null);
        chain = new StratumChain(params, store, client);
    }

    @Test
    public void checkpoint() throws IOException, ExecutionException, InterruptedException {
        Block block1 = makeBlock(params.getGenesisBlock().getHash());
        Block block2 = makeBlock(block1.getHash());
        File file1 = File.createTempFile("stratum-chain1", ".chain");
        HeadersStore store1 = new HeadersStore(params, file1, new StoredBlock(block1, BigInteger.ZERO, 10), null);
        StratumChain chain1 = new StratumChain(params, store1, client);
        expect(client.call("blockchain.block.get_header", 12)).andReturn(null);
        replay(client);
        assertEquals(10, store1.getHeight());
        assertTrue(chain1.handleBlock(11, block2));
        assertEquals(11, store1.getHeight());
        assertNotNull(store1.get(0));
        assertNull(store1.get(1));
        assertEquals(block1, store1.get(10));
        assertEquals(block2, store1.get(11));
        verify(client);
    }

    @Test
    public void add() throws ExecutionException, InterruptedException {
        Block block1 = makeBlock(params.getGenesisBlock().getHash());
        Block block2 = makeBlock(block1.getHash());
        expect(client.call("blockchain.block.get_header", 2)).andReturn(null);
        expect(client.call("blockchain.block.get_header", 3)).andReturn(null);
        replay(client);
        assertTrue(chain.handleBlock(1, block1));
        assertTrue(chain.handleBlock(2, block2));
        assertEquals(2, store.getHeight());
        assertEquals(params.getGenesisBlock(), store.get(0));
        assertEquals(block1, store.get(1));
        assertEquals(block2, store.get(2));
        verify(client);
    }

    @Test
    public void reorg() throws ExecutionException, InterruptedException {
        Block block1 = makeBlock(params.getGenesisBlock().getHash());
        Block block1b = makeBlock(params.getGenesisBlock().getHash());
        assertNotEquals(block1.getHash(), block1b.getHash());
        Block block2 = makeBlock(block1.getHash());
        Block block2a = makeBlock(block1.getHash());
        Block block2b = makeBlock(block1b.getHash());
        //immediateFuture(new StratumMessage(0L, blockToJson(block2a)))
        expect(client.call("blockchain.block.get_header", 2)).andReturn(null);
        expect(client.call("blockchain.block.get_header", 3)).andReturn(null);
        expect(client.call("blockchain.block.get_header", 3)).andReturn(null);
        expect(client.call("blockchain.block.get_header", 1)).andReturn(null);
        expect(client.call("blockchain.block.get_header", 2)).andReturn(null);
        expect(client.call("blockchain.block.get_header", 3)).andReturn(null);
        replay(client);
        assertTrue(chain.handleBlock(new StratumMessage(111L, StratumClient.BLOCKCHAIN_GET_HEADER, blockToJson(1, block1)))); // test JSON too
        assertTrue(chain.handleBlock(2, block2));
        assertTrue(chain.handleBlock(2, block2a)); // reorg length 1
        assertEquals(2, store.getHeight());
        assertEquals(store.top(), block2a);
        assertFalse(chain.handleBlock(2, block2b)); // reorg length 2
        assertTrue(chain.handleBlock(1, block1b));
        assertEquals(1, store.getHeight());
        assertEquals(store.top(), block1b);
        assertTrue(chain.handleBlock(2, block2b));
        assertEquals(2, store.getHeight());
        assertEquals(store.top(), block2b);
        verify(client);
    }

    private JsonNode blockToJson(long height, Block block) {
        return JsonNodeFactory.instance.objectNode()
                .put("timestamp", block.getTimeSeconds())
                .put("version", block.getVersion())
                .put("bits", block.getDifficultyTarget())
                .put("nonce", block.getNonce())
                .put("merkle_root", block.getMerkleRoot().toString())
                .put("block_height", height)
                .put("prev_block_hash", block.getPrevBlockHash().toString());
    }

    private Block makeBlock(Sha256Hash prev) {
        return new Block(params, 4, prev, Sha256Hash.ZERO_HASH, 100, 200, nonce++,
                Lists.<Transaction>newArrayList()).cloneAsHeader();
    }
}