pragma solidity 0.8.20;

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

import "../../src/FatsoERC20.sol";
import "../../src/GhostAuthority.sol";

import "./Permit.t.sol";
import "./Allowance.t.sol";
import "./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 maxAmount         = 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, maxAmount);
        initializeAllowance(alice, bob, address(token), amount, maxAmount, amount);
        initializeTransfer(alice, bob, address(token), amount, 0);
    }

    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, 69);
        assertEq(token.balanceOf(bob), 69);
    }

    function test_mint_couldNotBeDoneByArbitraryAccount() public {
        vm.expectRevert(GhostAccessControlled.Unauthorized.selector);
        vm.prank(deployer);
        token.mint(bob, 69);
        assertEq(token.balanceOf(bob), 0);
    }

    function test_mint_totalSupplyIncreases() public {
        assertEq(token.totalSupply(), 0);
        vm.prank(vault);
        token.mint(bob, 69);
        assertEq(token.totalSupply(), 69);
    }

    function test_burn_totalSupplyReduces() public {
        _mintTokens(bob, 69);
        vm.prank(bob);
        token.burn(69);
        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);
    }
}
