// 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 {USDWPublicWrapVault} from "../../contracts/bridge/integration/USDWPublicWrapVault.sol";
import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol";

contract MockNativeUSDW is ERC20 {
    uint8 private immutable _decimalsValue;

    constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) {
        _decimalsValue = decimals_;
    }

    function decimals() public view override returns (uint8) {
        return _decimalsValue;
    }

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

contract USDWPublicWrapVaultTest is Test {
    address internal admin = address(0xA11CE);
    address internal operator = address(0xB0B);
    address internal user = address(0xCAFE);
    address internal bridgeUser = address(0xD00D);

    MockNativeUSDW internal nativeUsdw;
    MockNativeUSDW internal strayToken;
    CompliantWrappedToken internal wrappedUsdw;
    USDWPublicWrapVault internal vault;

    function setUp() public {
        vm.startPrank(admin);
        nativeUsdw = new MockNativeUSDW("USD DWIN", "USDW", 18);
        strayToken = new MockNativeUSDW("Stray", "STRAY", 18);
        wrappedUsdw = new CompliantWrappedToken("Wrapped cUSDW", "cWUSDW", 6, admin);
        vault = new USDWPublicWrapVault(admin, address(nativeUsdw), address(wrappedUsdw));
        vault.grantRole(vault.RESERVE_OPERATOR_ROLE(), operator);
        wrappedUsdw.grantRole(wrappedUsdw.MINTER_ROLE(), address(vault));
        wrappedUsdw.grantRole(wrappedUsdw.BURNER_ROLE(), address(vault));
        vm.stopPrank();

        nativeUsdw.mint(user, 100e18);
        nativeUsdw.mint(operator, 100e18);
        strayToken.mint(address(vault), 5e18);
    }

    function testWrapNormalizesNativeAmountToWrappedDecimals() public {
        vm.startPrank(user);
        nativeUsdw.approve(address(vault), 5e18);
        uint256 wrappedAmount = vault.wrap(5e18, user);
        vm.stopPrank();

        assertEq(wrappedAmount, 5e6);
        assertEq(wrappedUsdw.balanceOf(user), 5e6);
        assertEq(nativeUsdw.balanceOf(address(vault)), 5e18);
        assertEq(nativeUsdw.balanceOf(user), 95e18);
    }

    function testUnwrapBurnsWrappedAndReleasesNativeUsd() public {
        vm.startPrank(user);
        nativeUsdw.approve(address(vault), 7e18);
        vault.wrap(7e18, user);
        uint256 unwrapped = vault.unwrap(2e6, user);
        vm.stopPrank();

        assertEq(unwrapped, 2e18);
        assertEq(wrappedUsdw.balanceOf(user), 5e6);
        assertEq(nativeUsdw.balanceOf(user), 95e18);
        assertEq(nativeUsdw.balanceOf(address(vault)), 5e18);
    }

    function testBridgeMintedSupplyCanUnwrapAgainstSeededLiquidity() public {
        vm.prank(operator);
        nativeUsdw.approve(address(vault), 20e18);
        vm.prank(operator);
        vault.seedLiquidity(20e18);

        vm.prank(admin);
        wrappedUsdw.mint(bridgeUser, 3e6);

        vm.prank(bridgeUser);
        uint256 released = vault.unwrap(3e6, bridgeUser);

        assertEq(released, 3e18);
        assertEq(nativeUsdw.balanceOf(bridgeUser), 3e18);
        assertEq(nativeUsdw.balanceOf(address(vault)), 17e18);
        assertEq(wrappedUsdw.balanceOf(bridgeUser), 0);
    }

    function testWrapRejectsNonCanonicalNativeAmount() public {
        vm.startPrank(user);
        nativeUsdw.approve(address(vault), 1e18 + 1);
        vm.expectRevert(abi.encodeWithSelector(USDWPublicWrapVault.NonCanonicalAmount.selector, 1e18 + 1));
        vault.wrap(1e18 + 1, user);
        vm.stopPrank();
    }

    function testPauseBlocksWrapAndUnwrap() public {
        vm.prank(admin);
        vault.pause();

        vm.startPrank(user);
        nativeUsdw.approve(address(vault), 1e18);
        vm.expectRevert();
        vault.wrap(1e18, user);
        vm.stopPrank();
    }

    function testRecoverNonUnderlyingTokenButProtectNativeReserve() public {
        vm.prank(admin);
        vault.recoverNonUnderlyingToken(address(strayToken), admin, 5e18);
        assertEq(strayToken.balanceOf(admin), 5e18);

        vm.prank(admin);
        vm.expectRevert(USDWPublicWrapVault.UnderlyingTokenProtected.selector);
        vault.recoverNonUnderlyingToken(address(nativeUsdw), admin, 1);
    }
}
