// 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 {CWNavOracle} from "../../contracts/cw-settlement/CWNavOracle.sol";
import {CWRedemptionQueue} from "../../contracts/cw-settlement/CWRedemptionQueue.sol";
import {CWStabilityFund} from "../../contracts/cw-settlement/CWStabilityFund.sol";

contract MockNavBridge {
    mapping(address => uint256) internal _locked;
    mapping(address => uint256) internal _outstanding;

    function setLocked(address token, uint256 amount) external {
        _locked[token] = amount;
    }

    function lockedBalance(address token) external view returns (uint256) {
        return _locked[token];
    }

    function totalOutstanding(address token) external view returns (uint256) {
        return _outstanding[token];
    }
}

contract MockReserveSystem {
    mapping(address => uint256) internal _balances;

    function setBalance(address asset, uint256 amount) external {
        _balances[asset] = amount;
    }

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

contract MockERC20Six is ERC20 {
    constructor() ERC20("Mock", "MOCK") {}

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

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

contract CWNavOracleTest is Test {
    MockNavBridge internal bridge;
    MockReserveSystem internal reserveSystem;
    CWNavOracle internal navOracle;
    MockERC20Six internal canonical;

    function setUp() public {
        bridge = new MockNavBridge();
        reserveSystem = new MockReserveSystem();
        navOracle = new CWNavOracle(address(this), address(bridge), address(reserveSystem));
        canonical = new MockERC20Six();
        navOracle.configureToken(address(canonical), address(canonical));
    }

    function test_publishNav_computesBackingAndNavPerShare() public {
        bridge.setLocked(address(canonical), 120 * 1e6);
        reserveSystem.setBalance(address(canonical), 30 * 1e6);

        navOracle.publishNav(
            address(canonical),
            100 * 1e6,
            8200,
            1500,
            300,
            keccak256("attestation-v1")
        );

        CWNavOracle.NavSnapshot memory snap = navOracle.getNavSnapshot(address(canonical));
        assertEq(snap.totalLockedAssets, 120 * 1e6);
        assertEq(snap.totalReserveAssets, 30 * 1e6);
        assertEq(snap.totalCwSupply, 100 * 1e6);
        assertEq(snap.backingRatioBps, 15000);
        assertEq(snap.navPerShare, 15e17);
    }
}

contract CWRedemptionQueueTest is Test {
    CWRedemptionQueue internal queue;
    MockERC20Six internal token;

    function setUp() public {
        queue = new CWRedemptionQueue(address(this));
        token = new MockERC20Six();
        queue.grantRole(queue.BRIDGE_ROLE(), address(this));
    }

    function test_instantTier_claimableImmediately() public {
        token.mint(address(this), 1_000 * 1e6);
        token.approve(address(queue), 1_000 * 1e6);

        uint256 requestId = queue.fundAndEnqueue(address(this), address(token), 1_000 * 1e6);
        queue.claim(requestId);
        assertEq(token.balanceOf(address(this)), 1_000 * 1e6);
    }

    function test_standardTier_requiresDelay() public {
        uint256 requestId = queue.enqueueRedemption(address(this), address(token), 10_000 * 1e6);
        vm.expectRevert();
        queue.claim(requestId);
        vm.warp(block.timestamp + 24 hours);
        queue.processDueRequests(_arr(requestId));
        vm.expectRevert();
        queue.claim(requestId);
    }

    function _arr(uint256 id) internal pure returns (uint256[] memory ids) {
        ids = new uint256[](1);
        ids[0] = id;
    }
}

contract CWStabilityFundTest is Test {
    CWStabilityFund internal fund;
    MockERC20Six internal token;

    function setUp() public {
        fund = new CWStabilityFund(address(this));
        token = new MockERC20Six();
        fund.setSupportedAsset(address(token), true);
    }

    function test_depositAndEmergencyWithdraw() public {
        token.mint(address(this), 100e6);
        token.approve(address(fund), 100e6);
        fund.deposit(address(token), 100e6);
        fund.emergencyWithdraw(address(token), address(this), 50e6, keccak256("depeg"));
        assertEq(token.balanceOf(address(this)), 50e6);
    }
}
