pragma solidity 0.8.20;

import {Test} from "forge-std/Test.sol";
import {ERC20} from "@openzeppelin-contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";

abstract contract ERC20AllowanceTest is Test {
    using SafeERC20 for ERC20;

    ERC20 tokenAllowance;
    uint256 amountAllowance;
    uint256 maxAmountAllowance;
    uint256 maxRealAmountAllowance;

    address aliceAllowance;
    address bobAllowance;

    function initializeAllowance(
        address alice,
        address bob,
        address token,
        uint256 amount,
        uint256 maxAmount,
        uint256 maxRealAmount
    ) public {
        tokenAllowance = ERC20(token);
        amountAllowance = amount;
        maxAmountAllowance = maxAmount;
        maxRealAmountAllowance = maxRealAmount;
        aliceAllowance = alice;
        bobAllowance = bob;
    }

    function test_allowance_couldApproveFunds() public {
        _mintAllowanceTokens(aliceAllowance, amountAllowance);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), 0);
        vm.prank(aliceAllowance);
        tokenAllowance.approve(bobAllowance, amountAllowance);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);
    }

    function test_allowance_transferFromDecreaseAllowance() public {
        _mintAllowanceTokens(aliceAllowance, amountAllowance);
        vm.prank(aliceAllowance);
        tokenAllowance.approve(bobAllowance, amountAllowance);
        assertEq(tokenAllowance.balanceOf(aliceAllowance), amountAllowance);
        assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);
        
        vm.prank(bobAllowance);
        tokenAllowance.safeTransferFrom(aliceAllowance, bobAllowance, amountAllowance);
        assertEq(tokenAllowance.balanceOf(aliceAllowance), 0);
        assertEq(tokenAllowance.balanceOf(bobAllowance), amountAllowance);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), 0);
    }

    function test_allowance_couldNotTransferFromIfNotEnoughFunds() public {
        _mintAllowanceTokens(aliceAllowance, amountAllowance);
        vm.prank(aliceAllowance);
        tokenAllowance.approve(bobAllowance, maxAmountAllowance);
        assertEq(tokenAllowance.balanceOf(aliceAllowance), amountAllowance);
        assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), maxAmountAllowance);

        vm.expectRevert();
        vm.prank(bobAllowance);
        assertEq(tokenAllowance.transferFrom(aliceAllowance, bobAllowance, maxAmountAllowance), false);
        assertEq(tokenAllowance.balanceOf(aliceAllowance), amountAllowance);
        assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), maxAmountAllowance);
    }

    function test_allowance_couldNotTransferFromIfNotEnoughAllowance() public {
        _mintAllowanceTokens(aliceAllowance, maxRealAmountAllowance);
        vm.prank(aliceAllowance);
        tokenAllowance.approve(bobAllowance, amountAllowance);
        assertEq(tokenAllowance.balanceOf(aliceAllowance), maxRealAmountAllowance);
        assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);

        vm.expectRevert();
        vm.prank(bobAllowance);
        assertEq(tokenAllowance.transferFrom(aliceAllowance, bobAllowance, maxAmountAllowance), false);
        assertEq(tokenAllowance.balanceOf(aliceAllowance), maxRealAmountAllowance);
        assertEq(tokenAllowance.balanceOf(bobAllowance), 0);
        assertEq(tokenAllowance.allowance(aliceAllowance, bobAllowance), amountAllowance);
    }

    function _mintAllowanceTokens(address who, uint256 value) internal virtual;
}
