import { expect, time, ether, toBN } from '../src/prelude';
import { timeIncreaseTo, fixSignature, signMessage, trackReceivedTokenAndTx, countInstructions } from '../src/utils';
import { randomHex, toHex } from 'web3-utils';

const TokenMock = artifacts.require('TokenMock');

describe('timeIncreaseTo', async function () {
    const precision = 2;

    async function shouldIncrease (secs: number) {
        const timeBefore = await time.latest();
        await timeIncreaseTo(timeBefore.addn(secs));
        const timeAfter = await time.latest();

        expect(timeAfter).to.be.bignumber.gt(timeBefore);
        expect(timeAfter.sub(timeBefore)).to.be.bignumber.lte(toBN(secs).addn(precision));
        expect(timeAfter.sub(timeBefore)).to.be.bignumber.gte(toBN(secs));
    }

    it('should be increased on 1000 sec', async function () {
        await shouldIncrease(1000);
    });

    it('should be increased on 2000 sec', async function () {
        await shouldIncrease(2000);
    });

    it('should be increased on 1000000 sec', async function () {
        await shouldIncrease(1000000);
    });

    it('should be thrown with increase time to a moment in the past', async function () {
        await expect(shouldIncrease(-1000)).to.eventually.be.rejectedWith(/Cannot increase current time \(\d+\) to a moment in the past \(\d+\)/);
    });
});

describe('fixSignature', async function () {
    it('should not be fixed geth sign', async function () {
        const signature = '0xb453386b73ba5608314e9b4c7890a4bd12cc24c2c7bdf5f87778960ff85c56a8520dabdbea357fc561120dd2625bd8a904f35bdb4b153cf706b6ff25bb0d898d1c';
        expect(signature).equal(fixSignature(signature));
    });

    it('should be fixed ganache sign', async function () {
        const signature = '0x511fafdf71306ff89a063a76b52656c18e9a7d80d19e564c90f0126f732696bb673cde46003aad0ccb6dab2ca91ae38b82170824b0725883875194b273f709b901';
        const v = parseInt(signature.slice(130, 132), 16) + 27;
        const vHex = v.toString(16);
        expect(signature.slice(0, 130) + vHex).equal(fixSignature(signature));
    });
});

contract('', function ([wallet1, wallet2]) {
    const initContext = async () => {
        const USDT = await TokenMock.new('USDT', 'USDT');
        const USDC = await TokenMock.new('USDC', 'USDC');
        return { USDT, USDC };
    };

    let context: Awaited<ReturnType<typeof initContext>> = undefined!;

    before(async () => {
        context = await initContext();
    });

    beforeEach(async function () {
        for (const addr of [wallet1, wallet2]) {
            for (const token of [context.USDT, context.USDC]) {
                await token.mint(addr, ether('1000'));
            }
        }
    });

    describe('signMessage', async function () {
        it('should be signed test1', async function () {
            expect(await web3.eth.sign('0x', wallet1)).equal(await signMessage(wallet1));
        });

        it('should be signed test2', async function () {
            const message = randomHex(32);
            expect(await web3.eth.sign(message, wallet1)).equal(await signMessage(wallet1, message));
        });

        it('should be signed test3', async function () {
            const message = toHex('Test message');
            expect(await web3.eth.sign(message, wallet1)).equal(await signMessage(wallet1, message));
        });
    });

    describe('trackReceivedTokenAndTx', async function () {
        it('should be tracked ERC20 Transfer', async function () {
            const [received, tx] = await trackReceivedTokenAndTx(
                context.USDT,
                wallet2,
                () => context.USDT.transfer(wallet2, ether('1'), { from: wallet1 }),
            );
            expect(received).to.be.bignumber.equal(ether('1'));
            expect(tx.tx.length).equal(66);
            expect(tx.receipt.from).equal(wallet1.toLowerCase());
            expect(tx.receipt.to).equal(context.USDT.address.toLowerCase());
            expect(tx.logs.length).equal(1);
            expect(tx.logs[0].event).equal('Transfer');
        });

        it('should be tracked ERC20 Approve', async function () {
            const [received, tx] = await trackReceivedTokenAndTx(
                context.USDT,
                wallet2,
                () => context.USDT.approve(wallet2, ether('1'), { from: wallet1 }),
            );
            expect(received).to.be.bignumber.equal('0');
            expect(tx.tx.length).equal(66);
            expect(tx.receipt.from).equal(wallet1.toLowerCase());
            expect(tx.receipt.to).equal(context.USDT.address.toLowerCase());
            expect(tx.logs.length).equal(1);
            expect(tx.logs[0].event).equal('Approval');
        });
    });

    describe('trackReceivedToken', async function () {
        it('should be tracked ERC20 Transfer', async function () {
            const [received] = await trackReceivedTokenAndTx(
                context.USDT,
                wallet2,
                () => context.USDT.transfer(wallet2, ether('1'), { from: wallet1 }),
            );
            expect(received).to.be.bignumber.equal(ether('1'));
        });

        it('should be tracked ERC20 Approve', async function () {
            const [received] = await trackReceivedTokenAndTx(
                context.USDT,
                wallet2,
                () => context.USDT.approve(wallet2, ether('1'), { from: wallet1 }),
            );
            expect(received).to.be.bignumber.equal('0');
        });
    });

    describe('countInstructions', async function () {
        it('should be counted ERC20 Transfer', async function () {
            const [, tx] = await trackReceivedTokenAndTx(
                context.USDT,
                wallet2,
                () => context.USDT.transfer(wallet2, ether('1'), { from: wallet1 }),
            );
            expect(await countInstructions(tx.receipt.transactionHash, ['STATICCALL', 'CALL', 'SSTORE', 'SLOAD']))
                .to.be.deep.equal([0, 0, 2, 2]);
        });

        it('should be counted ERC20 Approve', async function () {
            const [, tx] = await trackReceivedTokenAndTx(
                context.USDT,
                wallet2,
                () => context.USDT.approve(wallet2, ether('1'), { from: wallet1 }),
            );
            expect(await countInstructions(tx.receipt.transactionHash, ['STATICCALL', 'CALL', 'SSTORE', 'SLOAD']))
                .to.be.deep.equal([0, 0, 1, 0]);
        });
    });
});