const { expectEvent, expectRevert, constants } = require('@openzeppelin/test-helpers');
const gsn = require('@openzeppelin/gsn-helpers');
const { fixSignature } = require('../helpers/sign');
const { setGSNProvider } = require('../helpers/set-gsn-provider');
const { utils: { toBN } } = require('web3');
const { ZERO_ADDRESS } = constants;

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

contract('GSNRecipientSignature', function (accounts) {
  const [ signer, other ] = accounts;

  before(function () {
    setGSNProvider(GSNRecipientSignatureMock, accounts);
  });

  beforeEach(async function () {
    this.recipient = await GSNRecipientSignatureMock.new(signer);
  });

  context('when called directly', function () {
    it('mock function can be called', async function () {
      const { logs } = await this.recipient.mockFunction();
      expectEvent.inLogs(logs, 'MockFunctionCalled');
    });
  });

  context('when constructor is called with a zero address', function () {
    it('fails when constructor called with a zero address', async function () {
      await expectRevert(
        GSNRecipientSignatureMock.new(
          ZERO_ADDRESS,
        ),
        'GSNRecipientSignature: trusted signer is the zero address',
      );
    });
  });

  context('when relay-called', function () {
    beforeEach(async function () {
      await gsn.fundRecipient(web3, { recipient: this.recipient.address });
    });

    it('rejects unsigned relay requests', async function () {
      await gsn.expectError(this.recipient.mockFunction({ value: 0, useGSN: true }));
    });

    it('rejects relay requests where some parameters are signed', async function () {
      const approveFunction = async (data) =>
        fixSignature(
          await web3.eth.sign(
            web3.utils.soliditySha3(
              // the nonce is not signed
              // eslint-disable-next-line max-len
              data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas),
            ), signer,
          ),
        );

      await gsn.expectError(this.recipient.mockFunction({ value: 0, useGSN: true, approveFunction }));
    });

    it('accepts relay requests where all parameters are signed', async function () {
      const approveFunction = async (data) =>
        fixSignature(
          await web3.eth.sign(
            web3.utils.soliditySha3(
              // eslint-disable-next-line max-len
              data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas), toBN(data.nonce), data.relayHubAddress, data.to,
            ), signer,
          ),
        );

      const { tx } = await this.recipient.mockFunction({ value: 0, useGSN: true, approveFunction });

      await expectEvent.inTransaction(tx, GSNRecipientSignatureMock, 'MockFunctionCalled');
    });

    it('rejects relay requests where all parameters are signed by an invalid signer', async function () {
      const approveFunction = async (data) =>
        fixSignature(
          await web3.eth.sign(
            web3.utils.soliditySha3(
              // eslint-disable-next-line max-len
              data.relayerAddress, data.from, data.encodedFunctionCall, toBN(data.txFee), toBN(data.gasPrice), toBN(data.gas), toBN(data.nonce), data.relayHubAddress, data.to,
            ), other,
          ),
        );

      await gsn.expectError(this.recipient.mockFunction({ value: 0, useGSN: true, approveFunction }));
    });
  });
});
