const {
  time,
  loadFixture,
} = require('@nomicfoundation/hardhat-toolbox/network-helpers');
const { expect } = require('chai');
const { ethers } = require('hardhat');

describe('Onboarding', () => {
  const PRICE = 100;
  const STAKE_DURATION = 60;
  async function deploy() {
    // Contracts are deployed using the first signer/account by default
    const [account1, account2] = await ethers.getSigners();

    const DAO = await ethers.getContractFactory('DAO');
    const dao = await DAO.deploy();
    const Proposals = await ethers.getContractFactory('Proposals');
    const proposals = await Proposals.deploy(dao.target);
    const Onboarding = await ethers.getContractFactory('Onboarding');
    const onboarding = await Onboarding.deploy(dao.target, proposals.target, PRICE);

    await dao.addPost(account1, 'content-id');
    const callbackData = ethers.AbiCoder.defaultAbiCoder().encode([], []);
    await dao.initiateValidationPool(
      0,
      60,
      [1, 3],
      [1, 2],
      100,
      true,
      false,
      callbackData,
      { value: 100 },
    );
    await time.increase(61);
    await dao.evaluateOutcome(0);
    expect(await dao.balanceOf(account1)).to.equal(100);

    return {
      dao, onboarding, account1, account2,
    };
  }

  it('Should deploy', async () => {
    const { dao, onboarding, account1 } = await loadFixture(deploy);
    expect(dao).to.exist;
    expect(onboarding).to.exist;
    expect(await dao.memberCount()).to.equal(1);
    expect(await dao.balanceOf(account1)).to.equal(100);
    expect(await dao.totalSupply()).to.equal(100);
    expect(await onboarding.stakeCount()).to.equal(0);
  });

  describe('Work approval/disapproval', () => {
    let dao;
    let onboarding;
    let account1;
    let account2;

    beforeEach(async () => {
      ({
        dao, onboarding, account1, account2,
      } = await loadFixture(deploy));
      await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
    });

    it('should be able to submit work approval', async () => {
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await onboarding.submitWorkEvidence(0, 'evidence-content-id');
      await expect(onboarding.submitWorkApproval(0, true))
        .to.emit(dao, 'ValidationPoolInitiated').withArgs(1)
        .to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, true);
      const post = await dao.posts(1);
      expect(post.author).to.equal(account1);
      expect(post.sender).to.equal(onboarding.target);
      expect(post.contentId).to.equal('evidence-content-id');
      const pool = await dao.validationPools(1);
      expect(pool.postIndex).to.equal(1);
      expect(pool.fee).to.equal(PRICE * 0.9);
      expect(pool.sender).to.equal(onboarding.target);
    });

    it('should be able to submit work disapproval', async () => {
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await onboarding.submitWorkEvidence(0, 'evidence-content-id');
      await expect(onboarding.submitWorkApproval(0, false))
        .to.emit(dao, 'ValidationPoolInitiated').withArgs(1)
        .to.emit(onboarding, 'WorkApprovalSubmitted').withArgs(0, false);
    });

    it('should not be able to submit work approval/disapproval twice', async () => {
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await onboarding.submitWorkEvidence(0, 'evidence-content-id');
      await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
      await expect(onboarding.submitWorkApproval(0, true)).to.be.revertedWith('Status must be EvidenceSubmitted');
    });

    it('should not be able to submit work evidence after work approval', async () => {
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await onboarding.submitWorkEvidence(0, 'evidence-content-id');
      await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
      await expect(onboarding.submitWorkEvidence(0, 'evidence-content-id')).to.be.revertedWith('Status must be Requested');
    });

    it('should not be able to submit work approval/disapproval before work evidence', async () => {
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await expect(onboarding.submitWorkApproval(0, true)).to.be.revertedWith('Status must be EvidenceSubmitted');
    });
  });

  describe('Onboarding followup', () => {
    it('resolving the first validation pool should trigger a second pool', async () => {
      const {
        dao, onboarding, account2,
      } = await loadFixture(deploy);
      await dao.stakeAvailability(onboarding.target, 50, STAKE_DURATION);
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await onboarding.submitWorkEvidence(0, 'evidence-content-id');
      await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
      await time.increase(86401);
      await expect(dao.evaluateOutcome(1)).to.emit(dao, 'ValidationPoolInitiated').withArgs(2);
      expect(await dao.postCount()).to.equal(3);
      const post = await dao.posts(2);
      expect(post.author).to.equal(account2);
      expect(post.sender).to.equal(onboarding.target);
      expect(post.contentId).to.equal('req-content-id');
      const pool = await dao.validationPools(2);
      expect(pool.postIndex).to.equal(2);
      expect(pool.fee).to.equal(PRICE * 0.1);
      expect(pool.sender).to.equal(onboarding.target);
      expect(pool.fee);
    });

    it('if the first validation pool is rejected it should not trigger a second pool', async () => {
      const {
        dao, onboarding, account2,
      } = await loadFixture(deploy);
      await dao.stakeAvailability(onboarding.target, 40, STAKE_DURATION);
      await onboarding.connect(account2).requestWork('req-content-id', { value: PRICE });
      await onboarding.submitWorkEvidence(0, 'evidence-content-id');
      await expect(onboarding.submitWorkApproval(0, true)).to.emit(dao, 'ValidationPoolInitiated').withArgs(1);
      await dao.stakeOnValidationPool(1, 60, false);
      await time.increase(86401);
      await expect(dao.evaluateOutcome(1)).not.to.emit(dao, 'ValidationPoolInitiated');
      expect(await dao.postCount()).to.equal(2);
    });
  });
});