import { expect } from 'chai';
import { calcExpectedRewards } from '../utils/calculations';
import { SignerWithAddress } from '../make-suite';
import { ZERO_ADDRESS } from '../../../../helpers/constants';
import { tEthereumAddress } from '../../../../helpers/types';
import { IERC20Factory } from '../../../../types/IERC20Factory';
import { getRewardsAToken } from '../../../../helpers/contracts-getters';
import { BigNumber as EthersBigNumber } from '@ethersproject/bignumber';
import BigNumber from 'bignumber.js';
import '../utils/math';

export const checkRewards = async (
  user: SignerWithAddress,
  aToken: tEthereumAddress,
  block: number,
  shouldReward?: boolean,
  claimedToken?: tEthereumAddress,
  beforeBalanceClaimedToken?: EthersBigNumber
) => {
  const rewardAwareToken = await getRewardsAToken(aToken);
  const rewardsAvailable = await rewardAwareToken.getRewardsTokenAddressList();
  const userBalance = await rewardAwareToken.balanceOf(user.address, { blockTag: block - 1 });
  const totalRewardsBefore = new Array(rewardsAvailable.length);
  const userRewardsBefore = new Array(rewardsAvailable.length);
  const userIndexesBefore = new Array(rewardsAvailable.length);

  const totalRewardsAfter = new Array(rewardsAvailable.length);
  const userRewardsAfter = new Array(rewardsAvailable.length);
  const userIndexesAfter = new Array(rewardsAvailable.length);
  const userExpectedRewards = new Array(rewardsAvailable.length);

  for (let i = 0; i < rewardsAvailable.length; i++) {
    if (rewardsAvailable[i] == ZERO_ADDRESS) break;
    // Before action

    totalRewardsBefore[i] = await rewardAwareToken.getLifetimeRewards(rewardsAvailable[i], {
      blockTag: block - 1,
    });

    userRewardsBefore[i] = await rewardAwareToken.getUserRewardsAccrued(
      rewardsAvailable[i],
      user.address,
      {
        blockTag: block - 1,
      }
    );

    userIndexesBefore[i] = await rewardAwareToken.getUserIndex(rewardsAvailable[i], user.address, {
      blockTag: block - 1,
    });
    // After action
    totalRewardsAfter[i] = await rewardAwareToken.getLifetimeRewards(rewardsAvailable[i], {
      blockTag: block,
    });

    userRewardsAfter[i] = await rewardAwareToken.getUserRewardsAccrued(
      rewardsAvailable[i],
      user.address,
      {
        blockTag: block,
      }
    );

    userIndexesAfter[i] = await rewardAwareToken.getUserIndex(rewardsAvailable[i], user.address, {
      blockTag: block,
    });

    userExpectedRewards[i] = calcExpectedRewards(
      userBalance,
      userIndexesAfter[i],
      userIndexesBefore[i]
    );

    // Explicit check rewards when the test case expects rewards to the user
    if (shouldReward) {
      expect(userRewardsAfter[i]).to.be.gt('0');
      expect(userRewardsAfter[i]).to.eq(
        userRewardsBefore[i].add(userExpectedRewards[i]),
        `User rewards for token ${rewardsAvailable[i]} does not match`
      );
      if (beforeBalanceClaimedToken && rewardsAvailable[i] === claimedToken) {
        const reserveFactor = await rewardAwareToken.getRewardsReserveFactor();
        const totalRewards = userRewardsBefore[i].add(userExpectedRewards[i]);
        const priorClaimed = await rewardAwareToken.getUserClaimedRewards(
          claimedToken,
          user.address,
          {
            blockTag: block - 1,
          }
        );
        const totalClaim = totalRewards.sub(priorClaimed);
        const treasureRewards = EthersBigNumber.from(
          new BigNumber(totalClaim.toString())
            .percentMul(new BigNumber(reserveFactor.toString()))
            .toString()
        );
        const userRewards = totalClaim.sub(treasureRewards);

        const afterBalance = await IERC20Factory.connect(claimedToken, user.signer).balanceOf(
          user.address
        );

        expect(afterBalance).to.be.eq(beforeBalanceClaimedToken.add(userRewards));
      }
    } else {
      expect(userExpectedRewards[i]).to.be.eq('0', 'This action should not reward');
      expect(userRewardsBefore[i]).to.be.eq(userRewardsAfter[i], 'Rewards should stay the same');
    }
  }

  return;
};