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

import {Test} from "forge-std/Test.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

import {Chain138LPLocker} from "../../contracts/wrapped-lp-public/Chain138LPLocker.sol";
import {WLPReceiptToken} from "../../contracts/wrapped-lp-public/WLPReceiptToken.sol";
import {PublicChainMintController} from "../../contracts/wrapped-lp-public/PublicChainMintController.sol";
import {WLPRedemptionGateway} from "../../contracts/wrapped-lp-public/WLPRedemptionGateway.sol";
import {WrappedLPNAVVault} from "../../contracts/wrapped-lp-public/WrappedLPNAVVault.sol";
import {WLPNAVOracle} from "../../contracts/wrapped-lp-public/WLPNAVOracle.sol";

contract MockERC20 is ERC20 {
    constructor(string memory n, string memory s) ERC20(n, s) {}

    function mint(address to, uint256 a) external {
        _mint(to, a);
    }
}

contract WrappedLPProgramTest is Test {
    MockERC20 lp;
    Chain138LPLocker locker;
    WLPReceiptToken wlp;
    PublicChainMintController mintCtl;
    WLPRedemptionGateway gateway;

    address admin = address(this);
    address relayer = address(0xBEEF);
    address user = address(0xA11CE);

    function setUp() public {
        lp = new MockERC20("DODO LP", "DLP");
        locker = new Chain138LPLocker(address(lp), admin);
        wlp = new WLPReceiptToken("Wrapped LP", "wLP", 18, admin);
        mintCtl = new PublicChainMintController(address(wlp), address(locker), admin);
        gateway = new WLPRedemptionGateway(address(wlp), admin);

        wlp.grantRole(wlp.MINTER_ROLE(), address(mintCtl));
        wlp.grantRole(wlp.BURNER_ROLE(), address(gateway));
        mintCtl.grantRole(mintCtl.RELAYER_ROLE(), relayer);
        locker.grantRole(locker.BRIDGE_RELEASE_ROLE(), relayer);
    }

    function test_lock_mint_release_invariant() public {
        uint256 amt = 1000e18;
        lp.mint(user, amt);
        vm.startPrank(user);
        lp.approve(address(locker), amt);
        bytes32 lockRef = locker.deposit(amt);
        vm.stopPrank();

        assertEq(locker.totalEscrowed(), amt);

        vm.prank(relayer);
        mintCtl.mintForLock(lockRef, user, amt);
        assertEq(wlp.balanceOf(user), amt);

        vm.expectRevert();
        vm.prank(relayer);
        mintCtl.mintForLock(lockRef, user, amt);

        vm.prank(user);
        gateway.requestRedeem(amt);
        assertEq(wlp.balanceOf(user), 0);

        vm.prank(relayer);
        locker.releaseAmount(user, amt);
        assertEq(locker.totalEscrowed(), 0);
        assertEq(lp.balanceOf(user), amt);
    }

    function test_nav_vault_cap() public {
        MockERC20 usdc = new MockERC20("USDC", "USDC");
        WrappedLPNAVVault vault = new WrappedLPNAVVault(
            usdc,
            "Wrapped LP NAV Vault",
            "wLPV",
            admin
        );
        vault.setDepositCap(1_000_000e18);
        usdc.mint(user, 2_000_000e18);
        vm.startPrank(user);
        usdc.approve(address(vault), type(uint256).max);
        vm.expectRevert();
        vault.deposit(1_500_000e18, user);
        vm.stopPrank();
    }

    function test_nav_oracle_stale() public {
        WLPNAVOracle oracle = new WLPNAVOracle(admin, 3600);
        oracle.grantRole(oracle.KEEPER_ROLE(), relayer);
        vm.prank(relayer);
        oracle.submitAnswer(1e8);
        assertFalse(oracle.isStale());
        vm.warp(block.timestamp + 4000);
        assertTrue(oracle.isStale());
    }

    /// @notice Fuzz: distinct deposits yield distinct lockRefs and independent mints.
    function testFuzz_lockRef_unique(uint128 amtA, uint128 amtB) public {
        vm.assume(amtA > 0 && amtB > 0);
        vm.assume(amtA != amtB);

        uint256 a = uint256(amtA) * 1e10;
        uint256 b = uint256(amtB) * 1e10;

        lp.mint(user, a + b);
        vm.startPrank(user);
        lp.approve(address(locker), a + b);
        bytes32 rA = locker.deposit(a);
        bytes32 rB = locker.deposit(b);
        vm.stopPrank();

        assertTrue(rA != rB);

        vm.startPrank(relayer);
        mintCtl.mintForLock(rA, user, a);
        mintCtl.mintForLock(rB, user, b);
        vm.stopPrank();

        assertEq(wlp.balanceOf(user), a + b);
    }
}
