// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {IBurnMintERC20} from "../../../token/ERC20/IBurnMintERC20.sol";
import {IOptimismMintableERC20Minimal, IOptimismMintableERC20} from "../../../token/ERC20/IOptimismMintableERC20.sol";
import {IERC677} from "../../../token/ERC677/IERC677.sol";

import {BurnMintERC677} from "../../../token/ERC677/BurnMintERC677.sol";
import {BaseTest} from "../../BaseTest.t.sol";
import {OpStackBurnMintERC677} from "../../../token/ERC677/OpStackBurnMintERC677.sol";

import {IERC165} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/introspection/IERC165.sol";

contract OpStackBurnMintERC677Setup is BaseTest {
  address internal s_l1Token = address(897352983527);
  address internal s_l2Bridge = address(1928235235);

  OpStackBurnMintERC677 internal s_opStackBurnMintERC677;

  function setUp() public virtual override {
    BaseTest.setUp();
    s_opStackBurnMintERC677 = new OpStackBurnMintERC677("Chainlink Token", "LINK", 18, 1e27, s_l1Token, s_l2Bridge);
  }
}

contract OpStackBurnMintERC677_constructor is OpStackBurnMintERC677Setup {
  function testConstructorSuccess() public {
    string memory name = "Chainlink token l2";
    string memory symbol = "LINK L2";
    uint8 decimals = 18;
    uint256 maxSupply = 1e33;
    s_opStackBurnMintERC677 = new OpStackBurnMintERC677(name, symbol, decimals, maxSupply, s_l1Token, s_l2Bridge);

    assertEq(name, s_opStackBurnMintERC677.name());
    assertEq(symbol, s_opStackBurnMintERC677.symbol());
    assertEq(decimals, s_opStackBurnMintERC677.decimals());
    assertEq(maxSupply, s_opStackBurnMintERC677.maxSupply());
    assertEq(s_l1Token, s_opStackBurnMintERC677.remoteToken());
    assertEq(s_l2Bridge, s_opStackBurnMintERC677.bridge());
  }
}

contract OpStackBurnMintERC677_supportsInterface is OpStackBurnMintERC677Setup {
  function testConstructorSuccess() public {
    assertTrue(s_opStackBurnMintERC677.supportsInterface(type(IOptimismMintableERC20Minimal).interfaceId));
    assertTrue(s_opStackBurnMintERC677.supportsInterface(type(IERC677).interfaceId));
    assertTrue(s_opStackBurnMintERC677.supportsInterface(type(IBurnMintERC20).interfaceId));
    assertTrue(s_opStackBurnMintERC677.supportsInterface(type(IERC165).interfaceId));
  }
}

contract OpStackBurnMintERC677_interfaceCompatibility is OpStackBurnMintERC677Setup {
  event Transfer(address indexed from, address indexed to, uint256 value);

  IOptimismMintableERC20 internal s_opStackToken;

  function setUp() public virtual override {
    OpStackBurnMintERC677Setup.setUp();
    s_opStackToken = IOptimismMintableERC20(address(s_opStackBurnMintERC677));
  }

  function testStaticFunctionsCompatibility() public {
    assertEq(s_l1Token, s_opStackToken.remoteToken());
    assertEq(s_l2Bridge, s_opStackToken.bridge());
  }

  function testMintCompatibility() public {
    // Ensure roles work
    vm.expectRevert(abi.encodeWithSelector(BurnMintERC677.SenderNotMinter.selector, OWNER));
    s_opStackToken.mint(OWNER, 1);

    // Use the actual contract to grant mint
    s_opStackBurnMintERC677.grantMintRole(OWNER);

    // Ensure zero address check works
    vm.expectRevert("ERC20: mint to the zero address");
    s_opStackToken.mint(address(0x0), 0);

    address mintToAddress = address(0x1);
    uint256 mintAmount = 1;

    vm.expectEmit();
    emit Transfer(address(0), mintToAddress, mintAmount);

    s_opStackToken.mint(mintToAddress, mintAmount);
  }

  function testBurnCompatibility() public {
    // Ensure roles work
    vm.expectRevert(abi.encodeWithSelector(BurnMintERC677.SenderNotBurner.selector, OWNER));
    s_opStackToken.burn(address(0x0), 1);

    // Use the actual contract to grant burn
    s_opStackBurnMintERC677.grantBurnRole(OWNER);

    // Ensure zero address check works
    vm.expectRevert("ERC20: approve from the zero address");
    s_opStackToken.burn(address(0x0), 0);

    address burnFromAddress = address(0x1);
    uint256 burnAmount = 1;

    // Ensure `burn(address, amount)` works like burnFrom and requires allowance
    vm.expectRevert("ERC20: insufficient allowance");
    s_opStackToken.burn(burnFromAddress, burnAmount);

    changePrank(burnFromAddress);
    deal(address(s_opStackToken), burnFromAddress, burnAmount);
    s_opStackBurnMintERC677.approve(OWNER, burnAmount);
    changePrank(OWNER);

    vm.expectEmit();
    emit Transfer(burnFromAddress, address(0x0), burnAmount);

    s_opStackToken.burn(burnFromAddress, burnAmount);
  }
}
