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 ERC20TransferTest is Test {
    using SafeERC20 for ERC20;

    ERC20 tokenTransfer;
    uint256 amountTransfer;
    uint256 totalSupplyTransfer;

    address aliceTransfer;
    address bobTransfer;

    function initializeTransfer(
        address alice,
        address bob,
        address token,
        uint256 amount,
        uint256 totalSupply
    ) public {
        tokenTransfer = ERC20(token);
        amountTransfer = amount;
        totalSupplyTransfer = totalSupply;
        aliceTransfer = alice;
        bobTransfer = bob;
    }

    function test_transfer_tokenTransfers() public {
        _mintTransferTokens(aliceTransfer, amountTransfer);
        assertEq(tokenTransfer.balanceOf(aliceTransfer), amountTransfer);
        assertEq(tokenTransfer.balanceOf(bobTransfer), 0);
        vm.prank(aliceTransfer);
        tokenTransfer.safeTransfer(bobTransfer, amountTransfer);
        assertEq(tokenTransfer.balanceOf(bobTransfer), amountTransfer);
        assertEq(tokenTransfer.balanceOf(aliceTransfer), 0);
    }

    function test_transfer_transferFuzzing(uint64 fuzzingTransferAmount) public {
        if (totalSupplyTransfer == 0) vm.assume(fuzzingTransferAmount > 0);
        else vm.assume(fuzzingTransferAmount > 0 && fuzzingTransferAmount < totalSupplyTransfer);
        _mintTransferTokens(aliceTransfer, fuzzingTransferAmount);
        assertEq(tokenTransfer.balanceOf(aliceTransfer), fuzzingTransferAmount);
        assertEq(tokenTransfer.balanceOf(bobTransfer), 0);
        vm.prank(aliceTransfer);
        tokenTransfer.safeTransfer(bobTransfer, fuzzingTransferAmount);
        assertEq(tokenTransfer.balanceOf(bobTransfer), fuzzingTransferAmount);
        assertEq(tokenTransfer.balanceOf(aliceTransfer), 0);
    }

    function test_transfer_couldNotTransferMoreThanAvailable() public {
        _mintTransferTokens(aliceTransfer, amountTransfer);
        vm.expectRevert();
        vm.prank(aliceTransfer);
        assertEq(tokenTransfer.transfer(bobTransfer, amountTransfer * 2), false);
        assertEq(tokenTransfer.balanceOf(aliceTransfer), amountTransfer);
        assertEq(tokenTransfer.balanceOf(bobTransfer), 0);
    }

    function test_transfer_doesNotChangeTotalSupply() public {
        _mintTransferTokens(aliceTransfer, amountTransfer);
        assertEq(tokenTransfer.totalSupply(), _getCurrentTotalSupply());
        vm.prank(aliceTransfer);
        tokenTransfer.safeTransfer(bobTransfer, amountTransfer);
        assertEq(tokenTransfer.totalSupply(), _getCurrentTotalSupply());
    }

    function _mintTransferTokens(address who, uint256 value) internal virtual;

    function _getCurrentTotalSupply() internal virtual returns (uint256);
}
