pragma solidity 0.8.20;

import {Test} from "forge-std/Test.sol";

import {Fatso} from "../../src/FatsoERC20.sol";
import {GhostAuthority} from "../../src/GhostAuthority.sol";
import {GhostAccessControlled} from "../../src/types/GhostAccessControlled.sol";

import {ERC20PermitTest} from "./Permit.t.sol";
import {ERC20AllowanceTest} from "./Allowance.t.sol";
import {ERC20TransferTest} from "./Transfer.t.sol";

contract FatsoTest is Test, ERC20PermitTest, ERC20AllowanceTest, ERC20TransferTest {
    GhostAuthority authority;
    Fatso token;

    address constant DEPLOYER          = 0x0000000000000000000000000000000000000001;
    address constant VAULT             = 0x0000000000000000000000000000000000000002;
    address constant ALICE             = 0x0000000000000000000000000000000000000003;
    address constant BOB               = 0x0000000000000000000000000000000000000004;
    uint256 constant AMOUNT            = 69;
    uint256 constant MAX_AMOUNT        = type(uint256).max;

    string constant NAME               = "Fatso Test Name";
    string constant SYMBOL             = "FTSOTST";

    function setUp() public {
        authority = new GhostAuthority(
            DEPLOYER,
            DEPLOYER,
            DEPLOYER,
            VAULT
        );
        token = new Fatso(address(authority), NAME, SYMBOL);
        initializePermit(address(token), AMOUNT, MAX_AMOUNT);
        initializeAllowance(ALICE, BOB, address(token), AMOUNT, MAX_AMOUNT, AMOUNT);
        initializeTransfer(ALICE, BOB, address(token), AMOUNT, MAX_AMOUNT);
    }

    function test_mint_couldHappenFromVault() public {
        uint256 totalSupply = token.totalSupply();
        vm.prank(VAULT);
        token.mint(ALICE, AMOUNT);
        assertEq(token.totalSupply(), totalSupply + AMOUNT);
    }

    function test_mint_couldNotMintFromArbitraryAccount(address who) public {
        vm.assume(who != VAULT);
        vm.expectRevert();
        vm.prank(who);
        token.mint(ALICE, AMOUNT);
    }

    function test_correctlyConstructsAnERC20() public view {
        assertEq(token.name(), NAME);
        assertEq(token.symbol(), SYMBOL);
        assertEq(token.decimals(), 9);
    }

    function test_mint_couldBeDoneByVault() public {
        assertEq(token.balanceOf(BOB), 0);
        vm.prank(VAULT);
        token.mint(BOB, AMOUNT);
        assertEq(token.balanceOf(BOB), AMOUNT);
    }

    function test_mint_couldNotBeDoneByArbitraryAccount() public {
        vm.expectRevert(GhostAccessControlled.Unauthorized.selector);
        vm.prank(DEPLOYER);
        token.mint(BOB, AMOUNT);
        assertEq(token.balanceOf(BOB), 0);
    }

    function test_mint_totalSupplyIncreases() public {
        assertEq(token.totalSupply(), 0);
        vm.prank(VAULT);
        token.mint(BOB, AMOUNT);
        assertEq(token.totalSupply(), AMOUNT);
    }

    function test_burn_totalSupplyReduces() public {
        _mintTokens(BOB, AMOUNT);
        vm.prank(BOB);
        token.burn(AMOUNT);
        assertEq(token.totalSupply(), 0);
    }

    function test_burn_cannotExceedTotalSupply() public {
        _mintTokens(BOB, AMOUNT);
        vm.expectRevert();
        vm.prank(BOB);
        token.burn(AMOUNT + 1);
        assertEq(token.totalSupply(), AMOUNT);
    }

    function _mintTokens(address who, uint256 value) internal {
        uint256 totalSupply = token.totalSupply();
        vm.prank(VAULT);
        token.mint(who, value);
        assertEq(token.totalSupply(), totalSupply + value);
    }

    function _mintTransferTokens(address who, uint256 value) internal override {
        _mintTokens(who, value);
    }

    function _mintAllowanceTokens(address who, uint256 value) internal override {
        _mintTokens(who, value);
    }

    function _mintPermitTokens(address who, uint256 value) internal override {
        _mintTokens(who, value);
    }

    function _getCurrentTotalSupply() internal override view returns (uint256) {
        return token.totalSupply();
    }
}
