// 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 {IRouterClient} from "../../contracts/ccip/IRouterClient.sol";
import {CWMultiTokenBridgeL1} from "../../contracts/bridge/CWMultiTokenBridgeL1.sol";
import {CWReserveVerifier} from "../../contracts/bridge/integration/CWReserveVerifier.sol";

contract MockCanonicalBTCWithOwner is ERC20 {
    address internal _owner;

    constructor(address initialOwner) ERC20("Bitcoin (Compliant)", "cBTC") {
        _owner = initialOwner;
    }

    function decimals() public pure override returns (uint8) {
        return 8;
    }

    function owner() external view returns (address) {
        return _owner;
    }

    function setOwner(address newOwner) external {
        _owner = newOwner;
    }

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

contract MockRouterVerifierBTC is IRouterClient {
    function ccipSend(
        uint64,
        EVM2AnyMessage memory
    ) external payable returns (bytes32 messageId, uint256 fees) {
        return (bytes32(0), fees);
    }

    function getFee(uint64, EVM2AnyMessage memory) external pure returns (uint256) {
        return 0;
    }

    function getSupportedTokens(uint64) external pure returns (address[] memory tokens) {
        tokens = new address[](0);
    }
}

contract MockReserveVaultBTC {
    address internal trackedToken;
    mapping(address => uint256) public reserveBalance;
    mapping(address => uint256) public backingRatio;
    bool public tokenAdequate = true;

    constructor(address trackedToken_) {
        trackedToken = trackedToken_;
    }

    function compliantUSDT() external view returns (address) {
        return trackedToken;
    }

    function compliantUSDC() external pure returns (address) {
        return address(0xCAFE);
    }

    function setAdequacy(bool tokenAdequate_) external {
        tokenAdequate = tokenAdequate_;
    }

    function setBacking(address token, uint256 reserveBalance_, uint256 backingRatio_) external {
        reserveBalance[token] = reserveBalance_;
        backingRatio[token] = backingRatio_;
    }

    function getBackingRatio(address token) external view returns (uint256, uint256, uint256) {
        return (reserveBalance[token], ERC20(token).totalSupply(), backingRatio[token]);
    }

    function checkReserveAdequacy() external view returns (bool, bool) {
        return (tokenAdequate, true);
    }
}

contract MockReserveSystemBTC {
    mapping(address => uint256) internal reserveBalances;

    function setReserveBalance(address asset, uint256 amount) external {
        reserveBalances[asset] = amount;
    }

    function getReserveBalance(address asset) external view returns (uint256) {
        return reserveBalances[asset];
    }
}

contract CWReserveVerifierBTCTest is Test {
    uint64 internal constant ETHEREUM_SELECTOR = 5009297550715157269;

    address internal user = address(0xBEEF);
    address internal receiveRouter = address(0x138138);
    address internal peerBridge = address(0x010101);
    address internal reserveAsset = address(0x0B7C);

    MockRouterVerifierBTC internal router;
    MockCanonicalBTCWithOwner internal canonical;
    CWMultiTokenBridgeL1 internal l1Bridge;
    MockReserveVaultBTC internal reserveVault;
    MockReserveSystemBTC internal reserveSystem;
    CWReserveVerifier internal verifier;

    function setUp() public {
        router = new MockRouterVerifierBTC();
        canonical = new MockCanonicalBTCWithOwner(address(this));
        l1Bridge = new CWMultiTokenBridgeL1(address(router), receiveRouter, address(0));
        reserveVault = new MockReserveVaultBTC(address(canonical));
        reserveSystem = new MockReserveSystemBTC();
        verifier = new CWReserveVerifier(address(this), address(l1Bridge), address(reserveVault), address(reserveSystem));

        canonical.setOwner(address(reserveVault));
        l1Bridge.configureSupportedCanonicalToken(address(canonical), true);
        l1Bridge.configureDestination(address(canonical), ETHEREUM_SELECTOR, peerBridge, true);
        l1Bridge.setReserveVerifier(address(verifier));

        verifier.configureToken(address(canonical), reserveAsset, true, true, true);

        canonical.mint(user, 10_000_000_000);
        reserveVault.setBacking(address(canonical), canonical.totalSupply(), 10_000);
        reserveVault.setAdequacy(true);
        reserveSystem.setReserveBalance(reserveAsset, canonical.totalSupply());
    }

    function testVerifierAllowsBTCLockAtEightDecimals() public {
        vm.startPrank(user);
        canonical.approve(address(l1Bridge), 250_000_000);
        l1Bridge.lockAndSend(address(canonical), ETHEREUM_SELECTOR, user, 250_000_000);
        vm.stopPrank();

        assertEq(l1Bridge.totalOutstanding(address(canonical)), 250_000_000);
        assertEq(l1Bridge.lockedBalance(address(canonical)), 250_000_000);
    }

    function testVerifierBlocksBTCLockWhenReserveFallsShort() public {
        reserveSystem.setReserveBalance(reserveAsset, canonical.totalSupply() - 1);

        vm.startPrank(user);
        canonical.approve(address(l1Bridge), 100_000_000);
        vm.expectRevert(CWReserveVerifier.ReserveSystemBackingInsufficient.selector);
        l1Bridge.lockAndSend(address(canonical), ETHEREUM_SELECTOR, user, 100_000_000);
        vm.stopPrank();
    }
}
