// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import "./TestBase.sol";
import {FleekBilling} from "contracts/FleekBilling.sol";
import "contracts/FleekAccessControl.sol";

contract Test_FleekERC721_AccessControlAssertions is Test {
    function expectRevertWithMustHaveAtLeastOneOwner() internal {
        vm.expectRevert(abi.encodeWithSelector(MustHaveAtLeastOneOwner.selector));
    }

    function expectRevertWithRoleAlreadySet() internal {
        vm.expectRevert(abi.encodeWithSelector(RoleAlreadySet.selector));
    }
}

contract Test_FleekERC721_AccessControl is Test_FleekERC721_Base, Test_FleekERC721_AccessControlAssertions {
    uint256 internal tokenId;
    address internal collectionOwner = address(100);
    address internal collectionVerifier = address(101);
    address internal tokenOwner = address(200);
    address internal tokenController = address(300);
    address internal anyAddress = address(400);

    function setUp() public {
        baseSetUp();

        // Set collectionOwner
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
        // Set collectionVerifier
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, collectionVerifier);
        // Mint to tokenOwner to set tokenOwner
        mintDefault(tokenOwner);
        // Set tokenController to minted token
        vm.startPrank(tokenOwner);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController);
        CuT.setTokenVerifier(tokenId, collectionVerifier);
        vm.stopPrank();
    }

    function test_setUp() public {
        // Check deployer
        assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, deployer));
        assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, deployer));

        // Check collectionOwner
        assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner));
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, collectionOwner));
        assertFalse(CuT.ownerOf(tokenId) == collectionOwner);
        assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, collectionOwner));
        // Check collectionVerifier
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionVerifier));
        assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, collectionVerifier));
        assertFalse(CuT.ownerOf(tokenId) == collectionVerifier);
        assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, collectionVerifier));
        // Check tokenOwner
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, tokenOwner));
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, tokenOwner));
        assertTrue(CuT.ownerOf(tokenId) == tokenOwner);
        assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenOwner));
        // Check tokenController
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, tokenController));
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, tokenController));
        assertFalse(CuT.ownerOf(tokenId) == tokenController);
        assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController));
        // Check anyAddress
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, anyAddress));
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Verifier, anyAddress));
        assertFalse(CuT.ownerOf(tokenId) == anyAddress);
        assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, anyAddress));
    }

    function test_grantAndRevokeCollectionRole() public {
        address randomAddress = address(99);

        // CollectionOwner
        vm.startPrank(collectionOwner);
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        assertTrue(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress));
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        assertFalse(CuT.hasCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress));
        vm.stopPrank();

        // CollectionVerifier
        vm.startPrank(collectionVerifier);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        vm.stopPrank();

        // TokenOwner
        vm.startPrank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        vm.stopPrank();

        // TokenController
        vm.startPrank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        vm.stopPrank();

        // AnyAddress
        vm.startPrank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, randomAddress);
        vm.stopPrank();
    }

    function test_grantAndRevokeTokenRole() public {
        address randomAddress = address(99);

        // CollectionOwner
        vm.startPrank(collectionOwner);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        vm.stopPrank();

        // CollectionVerifier
        vm.startPrank(collectionVerifier);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        vm.stopPrank();

        // TokenOwner
        vm.startPrank(tokenOwner);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        assertTrue(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress));
        CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        assertFalse(CuT.hasTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress));
        vm.stopPrank();

        // TokenController
        vm.startPrank(tokenController);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        vm.stopPrank();

        // AnyAddress
        vm.startPrank(anyAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, randomAddress);
        vm.stopPrank();
    }

    function test_mint() public {
        // Anyone can mint
        transferENS(TestConstants.APP_ENS, anyAddress);
        vm.startPrank(anyAddress);
        mintDefault(address(99));
        vm.stopPrank();
    }

    function test_tokenURI() public {
        // Anyone can get the tokenURI
        vm.prank(anyAddress);
        CuT.tokenURI(tokenId);
    }

    function test_setTokenExternalURL() public {
        string memory externalURL = "https://externalurl.com";

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenExternalURL(tokenId, externalURL);

        // VerifierRole
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenExternalURL(tokenId, externalURL);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenExternalURL(tokenId, externalURL);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenExternalURL(tokenId, externalURL);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenExternalURL(tokenId, externalURL);
    }

    function test_setTokenENS() public {
        string memory ens = "ens.eth";

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenENS(tokenId, ens);

        // VerifierRole
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenENS(tokenId, ens);

        // TokenOwner
        transferENS(ens, tokenOwner);
        vm.prank(tokenOwner);
        CuT.setTokenENS(tokenId, ens);

        // TokenController
        transferENS(ens, tokenController);
        vm.prank(tokenController);
        CuT.setTokenENS(tokenId, ens);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenENS(tokenId, ens);
    }

    function test_setTokenName() public {
        string memory name = "name";

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenName(tokenId, name);

        // VerifierRole
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenName(tokenId, name);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenName(tokenId, name);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenName(tokenId, name);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenName(tokenId, name);
    }

    function test_setTokenDescription() public {
        string memory description = "description";

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenDescription(tokenId, description);

        // VerifierRole
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenDescription(tokenId, description);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenDescription(tokenId, description);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenDescription(tokenId, description);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenDescription(tokenId, description);
    }

    function test_setTokenLogo() public {
        string memory logo = "logo";

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenLogo(tokenId, logo);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenLogo(tokenId, logo);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenLogo(tokenId, logo);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenLogo(tokenId, logo);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenLogo(tokenId, logo);
    }

    function test_setTokenColor() public {
        uint24 color = 0x000000;

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenColor(tokenId, color);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenColor(tokenId, color);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenColor(tokenId, color);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenColor(tokenId, color);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenColor(tokenId, color);
    }

    function test_setTokenLogoAndColor() public {
        string memory logo = "logo";
        uint24 color = 0x000000;

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenLogoAndColor(tokenId, logo, color);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenLogoAndColor(tokenId, logo, color);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenLogoAndColor(tokenId, logo, color);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenLogoAndColor(tokenId, logo, color);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenLogoAndColor(tokenId, logo, color);
    }

    function test_setTokenBuild() public {
        string memory commitHash = "commitHash";
        string memory gitRepository = "gitRepository";

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenBuild(tokenId, commitHash, gitRepository);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenBuild(tokenId, commitHash, gitRepository);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenBuild(tokenId, commitHash, gitRepository);

        // TokenController
        vm.prank(tokenController);
        CuT.setTokenBuild(tokenId, commitHash, gitRepository);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller);
        CuT.setTokenBuild(tokenId, commitHash, gitRepository);
    }

    function test_burn() public {
        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.burn(tokenId);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.burn(tokenId);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.burn(tokenId);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.burn(tokenId);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.burn(tokenId);
    }

    function test_setAccessPointContentVerify() public {
        string memory apName = "random.com";
        CuT.addAccessPoint(tokenId, apName);

        // CollectionOwner
        vm.prank(collectionOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointContentVerify(apName, true);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        CuT.setAccessPointContentVerify(apName, true);

        // TokenOwner
        vm.prank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointContentVerify(apName, false);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointContentVerify(apName, false);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointContentVerify(apName, false);
    }

    function test_setAccessPointNameVerify() public {
        string memory apName = "random.com";
        CuT.addAccessPoint(tokenId, apName);

        // CollectionOwner
        vm.prank(collectionOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointNameVerify(apName, true);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        CuT.setAccessPointNameVerify(apName, true);

        // TokenOwner
        vm.prank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointNameVerify(apName, false);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointNameVerify(apName, false);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setAccessPointNameVerify(apName, false);
    }

    function test_setBilling() public {
        // ColletionOwner
        vm.prank(collectionOwner);
        CuT.setBilling(FleekBilling.Billing.Mint, 1 ether);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setBilling(FleekBilling.Billing.Mint, 2 ether);

        // TokenOwner
        vm.prank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setBilling(FleekBilling.Billing.Mint, 2 ether);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setBilling(FleekBilling.Billing.Mint, 2 ether);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setBilling(FleekBilling.Billing.Mint, 2 ether);
    }

    function test_withdraw() public {
        // ColletionOwner
        vm.deal(address(CuT), 1 ether);
        vm.prank(collectionOwner);
        CuT.withdraw();

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.withdraw();

        // TokenOwner
        vm.prank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.withdraw();

        // TokenController
        vm.prank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.withdraw();

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.withdraw();
    }

    function test_pauseAndUnpause() public {
        // ColletionOwner
        vm.startPrank(collectionOwner);
        CuT.pause();
        CuT.unpause();
        vm.stopPrank();

        // CollectionVerifier
        vm.startPrank(collectionVerifier);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.pause();
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.unpause();
        vm.stopPrank();

        // TokenOwner
        vm.startPrank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.pause();
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.unpause();
        vm.stopPrank();

        // TokenController
        vm.startPrank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.pause();
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.unpause();
        vm.stopPrank();

        // AnyAddress
        vm.startPrank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.pause();
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.unpause();
        vm.stopPrank();
    }

    function test_setPausable() public {
        // ColletionOwner
        vm.prank(collectionOwner);
        CuT.setPausable(false);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setPausable(true);

        // TokenOwner
        vm.prank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setPausable(true);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setPausable(true);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Owner);
        CuT.setPausable(true);
    }

    function test_setTokenVerifier() public {
        address otherVerifier = address(0x1234);
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Verifier, otherVerifier);

        // ColletionOwner
        vm.prank(collectionOwner);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.setTokenVerifier(tokenId, otherVerifier);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.setTokenVerifier(tokenId, otherVerifier);

        // TokenOwner
        vm.prank(tokenOwner);
        CuT.setTokenVerifier(tokenId, otherVerifier);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.setTokenVerifier(tokenId, collectionVerifier);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithMustBeTokenOwner(tokenId);
        CuT.setTokenVerifier(tokenId, collectionVerifier);
    }

    function test_setTokenVerified() public {
        // CollectionOwner
        vm.prank(collectionOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setTokenVerified(tokenId, true);

        // CollectionVerifier
        vm.prank(collectionVerifier);
        CuT.setTokenVerified(tokenId, true);

        // TokenOwner
        vm.prank(tokenOwner);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setTokenVerified(tokenId, false);

        // TokenController
        vm.prank(tokenController);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setTokenVerified(tokenId, false);

        // AnyAddress
        vm.prank(anyAddress);
        expectRevertWithCollectionRole(FleekAccessControl.CollectionRoles.Verifier);
        CuT.setTokenVerified(tokenId, false);
    }

    function test_cannotHaveLessThanOneCollectionOwner() public {
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);
        expectRevertWithMustHaveAtLeastOneOwner();
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, deployer);
    }

    function test_cannotGrantRoleAlreadyGaranted() public {
        expectRevertWithRoleAlreadySet();
        CuT.grantCollectionRole(FleekAccessControl.CollectionRoles.Owner, collectionOwner);

        expectRevertWithRoleAlreadySet();
        vm.prank(tokenOwner);
        CuT.grantTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, tokenController);
    }

    function test_cannotRevokeRoleAlreadyRevoked() public {
        expectRevertWithRoleAlreadySet();
        CuT.revokeCollectionRole(FleekAccessControl.CollectionRoles.Owner, anyAddress);

        expectRevertWithRoleAlreadySet();
        vm.prank(tokenOwner);
        CuT.revokeTokenRole(tokenId, FleekAccessControl.TokenRoles.Controller, anyAddress);
    }

    /**
     * @dev `receive` and `fallback` are required for test contract receive ETH
     */
    receive() external payable {}

    fallback() external payable {}
}
