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

import {Test, console} from "forge-std/Test.sol";
import {Stabilizer} from "../../../../contracts/bridge/trustless/integration/Stabilizer.sol";
import {PrivatePoolRegistry} from "../../../../contracts/dex/PrivatePoolRegistry.sol";
import {IStablecoinPegManager} from "../../../../contracts/bridge/trustless/integration/IStablecoinPegManager.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockStablecoinPegManager is IStablecoinPegManager {
    int256 public deviationBps;

    function setDeviation(int256 _deviationBps) external {
        deviationBps = _deviationBps;
    }

    function checkUSDpeg(address) external view returns (bool isMaintained, int256 _deviationBps) {
        _deviationBps = deviationBps;
        isMaintained = deviationBps >= -50 && deviationBps <= 50;
    }

    function checkETHpeg(address) external view returns (bool, int256) {
        return (true, deviationBps);
    }

    function calculateDeviation(address, uint256, uint256) external pure returns (int256) {
        return 0;
    }

    function getPegStatus(address) external view returns (uint256, uint256, int256, bool) {
        return (1e18, 1e18, deviationBps, true);
    }

    function getSupportedAssets() external pure returns (address[] memory) {
        return new address[](0);
    }
}

contract MockDODOPool {
    address public baseToken;
    address public quoteToken;
    uint256 public midPrice = 1e18;
    uint256 public sellOutAmount = 1e18;

    constructor(address _base, address _quote) {
        baseToken = _base;
        quoteToken = _quote;
    }

    function _BASE_TOKEN_() external view returns (address) { return baseToken; }
    function _QUOTE_TOKEN_() external view returns (address) { return quoteToken; }
    function getMidPrice() external view returns (uint256) { return midPrice; }

    function sellBase(address) external returns (uint256) {
        return sellOutAmount;
    }

    function sellQuote(address) external returns (uint256) {
        return sellOutAmount;
    }

    function setSellOutAmount(uint256 v) external { sellOutAmount = v; }
    function setMidPrice(uint256 v) external { midPrice = v; }
}

contract MockERC20 {
    mapping(address => uint256) public balanceOf;
    function mint(address to, uint256 amount) external {
        balanceOf[to] += amount;
    }
    function transfer(address to, uint256 amount) external returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        return true;
    }
    function approve(address, uint256) external pure returns (bool) { return true; }
}

contract StabilizerTest is Test {
    Stabilizer public stabilizer;
    PrivatePoolRegistry public registry;
    MockStablecoinPegManager public pegManager;
    MockDODOPool public mockPool;
    MockERC20 public tokenIn;
    MockERC20 public tokenOut;

    address public admin = address(0x1);
    address public keeper = address(0x2);

    function setUp() public {
        registry = new PrivatePoolRegistry(admin);
        stabilizer = new Stabilizer(admin, address(registry));
        pegManager = new MockStablecoinPegManager();
        tokenIn = new MockERC20();
        tokenOut = new MockERC20();
        tokenIn.mint(address(stabilizer), 1000e18);
        mockPool = new MockDODOPool(address(tokenIn), address(tokenOut));

        vm.startPrank(admin);
        stabilizer.grantRole(stabilizer.STABILIZER_KEEPER_ROLE(), keeper);
        stabilizer.setStablecoinPegSource(address(pegManager), address(tokenIn));
        stabilizer.setMinBlocksBetweenExecution(3);
        stabilizer.setMaxStabilizationVolumePerBlock(100e18);
        stabilizer.setThresholdBps(50);
        stabilizer.setSustainedDeviationBlocks(3);
        stabilizer.setMaxSlippageBps(100);
        registry.register(address(tokenIn), address(tokenOut), address(mockPool));
        vm.stopPrank();
    }

    function test_checkDeviation_belowThreshold_returnsNoRebalance() public {
        pegManager.setDeviation(30); // 0.3% < 50 bps
        (int256 dev, bool shouldRebalance) = stabilizer.checkDeviation();
        assertEq(dev, 30);
        assertFalse(shouldRebalance);
    }

    function test_checkDeviation_aboveThreshold_singleBlock_noSustained_returnsNoRebalance() public {
        pegManager.setDeviation(100);
        (int256 dev, bool shouldRebalance) = stabilizer.checkDeviation();
        assertEq(dev, 100);
        assertFalse(shouldRebalance); // no samples yet
    }

    function test_checkDeviation_aboveThreshold_sustained_returnsRebalance() public {
        pegManager.setDeviation(100);
        vm.prank(keeper);
        stabilizer.recordDeviation();
        vm.roll(block.number + 1);
        stabilizer.recordDeviation();
        vm.roll(block.number + 1);
        stabilizer.recordDeviation();
        (int256 dev, bool shouldRebalance) = stabilizer.checkDeviation();
        assertEq(dev, 100);
        assertTrue(shouldRebalance);
    }

    function test_executePrivateSwap_revertWhenBlockDelayNotMet() public {
        pegManager.setDeviation(100);
        for (uint256 i = 0; i < 3; i++) {
            stabilizer.recordDeviation();
            vm.roll(block.number + 1);
        }
        vm.roll(2); // block 2 < lastExecutionBlock(0) + 3
        vm.prank(keeper);
        vm.expectRevert(Stabilizer.BlockDelayNotMet.selector);
        stabilizer.executePrivateSwap(10e18, address(tokenIn), address(tokenOut));
    }

    function test_executePrivateSwap_revertWhenVolumeCapExceeded() public {
        pegManager.setDeviation(100);
        for (uint256 i = 0; i < 4; i++) {
            stabilizer.recordDeviation();
            vm.roll(block.number + 1);
        }
        vm.roll(block.number + 10);
        vm.prank(keeper);
        vm.expectRevert(Stabilizer.VolumeCapExceeded.selector);
        stabilizer.executePrivateSwap(200e18, address(tokenIn), address(tokenOut));
    }

    function test_executePrivateSwap_revertWhenSlippageExceeded() public {
        pegManager.setDeviation(100);
        for (uint256 i = 0; i < 4; i++) {
            stabilizer.recordDeviation();
            vm.roll(block.number + 1);
        }
        vm.roll(block.number + 10);
        mockPool.setSellOutAmount(1); // very low output => slippage
        vm.prank(keeper);
        vm.expectRevert(Stabilizer.SlippageExceeded.selector);
        stabilizer.executePrivateSwap(10e18, address(tokenIn), address(tokenOut));
    }

    function test_executePrivateSwap_revertWhenNoPrivatePool() public {
        vm.startPrank(admin);
        MockERC20 otherIn = new MockERC20();
        MockERC20 otherOut = new MockERC20();
        otherIn.mint(address(stabilizer), 1000e18);
        vm.stopPrank();
        pegManager.setDeviation(100);
        for (uint256 i = 0; i < 4; i++) {
            stabilizer.recordDeviation();
            vm.roll(block.number + 1);
        }
        vm.roll(block.number + 10);
        vm.prank(keeper);
        vm.expectRevert(Stabilizer.NoPrivatePool.selector);
        stabilizer.executePrivateSwap(10e18, address(otherIn), address(otherOut));
    }

    function test_executePrivateSwap_revertWhenShouldNotRebalance() public {
        pegManager.setDeviation(30); // below threshold
        vm.roll(block.number + 10);
        vm.prank(keeper);
        vm.expectRevert(Stabilizer.ShouldNotRebalance.selector);
        stabilizer.executePrivateSwap(10e18, address(tokenIn), address(tokenOut));
    }
}
