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

import {Test, console} from "forge-std/Test.sol";
import "../../../contracts/bridge/trustless/BondManager.sol";
import "../../../contracts/bridge/trustless/ChallengeManager.sol";
import "../../../contracts/bridge/trustless/InboxETH.sol";
import "../../../contracts/bridge/trustless/LiquidityPoolETH.sol";
import "../../../contracts/bridge/trustless/EnhancedSwapRouter.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        _mint(msg.sender, 1000000 ether);
    }
}

contract FuzzTests is Test {
    BondManager public bondManager;
    ChallengeManager public challengeManager;
    LiquidityPoolETH public liquidityPool;
    InboxETH public inbox;
    EnhancedSwapRouter public swapRouter;
    
    MockERC20 public weth;
    MockERC20 public usdt;
    MockERC20 public usdc;
    MockERC20 public dai;

    address public deployer = address(0xDE0001);
    address public relayer = address(0x1111);
    address public lp = address(0x2222);

    uint256 public constant BOND_MULTIPLIER = 11000; // 110% in basis points
    uint256 public constant MIN_BOND = 1 ether;
    uint256 public constant CHALLENGE_WINDOW = 30 minutes;

    // Mock protocol addresses
    address public uniswapV3Router = address(0x1111111111111111111111111111111111111111);
    address public curve3Pool = address(0x2222222222222222222222222222222222222222);
    address public dodoexRouter = address(0x3333333333333333333333333333333333333333);
    address public balancerVault = address(0x4444444444444444444444444444444444444444);
    address public oneInchRouter = address(0x5555555555555555555555555555555555555555);

    function setUp() public {
        vm.startPrank(deployer);

        weth = new MockERC20("Wrapped Ether", "WETH");
        usdt = new MockERC20("Tether USD", "USDT");
        usdc = new MockERC20("USD Coin", "USDC");
        dai = new MockERC20("Dai Stablecoin", "DAI");

        bondManager = new BondManager(BOND_MULTIPLIER, MIN_BOND);
        challengeManager = new ChallengeManager(address(bondManager), CHALLENGE_WINDOW);
        liquidityPool = new LiquidityPoolETH(address(weth), 5, 11000);
        inbox = new InboxETH(address(bondManager), address(challengeManager), address(liquidityPool));
        
        swapRouter = new EnhancedSwapRouter(
            uniswapV3Router,
            curve3Pool,
            dodoexRouter,
            balancerVault,
            oneInchRouter,
            address(weth),
            address(usdt),
            address(usdc),
            address(dai)
        );

        liquidityPool.authorizeRelease(address(inbox));
        swapRouter.grantRole(swapRouter.ROUTING_MANAGER_ROLE(), deployer);

        vm.deal(relayer, 10000 ether);
        vm.deal(lp, 100000 ether);
        vm.warp(1000);

        // Provide liquidity
        vm.stopPrank();
        vm.prank(lp);
        liquidityPool.provideLiquidity{value: 1000 ether}(LiquidityPoolETH.AssetType.ETH);
    }

    /// @notice Fuzz test: Bond calculation should always be >= MIN_BOND
    function testFuzz_BondCalculation(uint256 amount) public {
        // Bound amount to reasonable range (1 wei to 1000000 ether)
        amount = bound(amount, 1, 1000000 ether);
        
        uint256 requiredBond = bondManager.getRequiredBond(amount);
        
        // Bond should always be at least MIN_BOND
        assertGe(requiredBond, MIN_BOND, "Bond should be at least MIN_BOND");
        
        // Bond should be at least amount * multiplier
        uint256 minExpectedBond = (amount * BOND_MULTIPLIER) / 10000;
        assertGe(requiredBond, minExpectedBond, "Bond should respect multiplier");
    }

    /// @notice Fuzz test: Multiple claims with random amounts
    function testFuzz_MultipleClaims(
        uint256[10] memory amounts,
        uint256[10] memory depositIds
    ) public {
        // Bound amounts to reasonable range
        for (uint256 i = 0; i < 10; i++) {
            amounts[i] = bound(amounts[i], 1 ether, 100 ether);
            depositIds[i] = bound(depositIds[i], 1, type(uint256).max);
            
            uint256 bond = bondManager.getRequiredBond(amounts[i]);
            
            // Only proceed if relayer has enough ETH
            if (bond <= address(relayer).balance) {
                vm.prank(relayer);
                try inbox.submitClaim{value: bond}(
                    depositIds[i],
                    address(0),
                    amounts[i],
                    address(0x3333),
                    ""
                ) {
                    // Verify bond was posted
                    (, uint256 bondAmount, , , ) = bondManager.bonds(depositIds[i]);
                    assertGe(bondAmount, MIN_BOND, "Bond should be posted");
                } catch {
                    // Some reverts are acceptable (e.g., duplicate depositId)
                }
            }
        }
    }

    /// @notice Fuzz test: Routing configuration with random providers
    function testFuzz_RoutingConfig(
        uint8 provider1,
        uint8 provider2,
        uint8 provider3
    ) public {
        // Bound to valid provider enum values (0-4)
        provider1 = uint8(bound(provider1, 0, 4));
        provider2 = uint8(bound(provider2, 0, 4));
        provider3 = uint8(bound(provider3, 0, 4));
        
        EnhancedSwapRouter.SwapProvider[] memory providers = new EnhancedSwapRouter.SwapProvider[](3);
        providers[0] = EnhancedSwapRouter.SwapProvider(provider1);
        providers[1] = EnhancedSwapRouter.SwapProvider(provider2);
        providers[2] = EnhancedSwapRouter.SwapProvider(provider3);
        
        vm.prank(deployer);
        try swapRouter.setRoutingConfig(0, providers) {
            // Success - config was set
            assertTrue(true);
        } catch {
            // Revert is acceptable for invalid configurations
            assertTrue(true);
        }
    }

    /// @notice Fuzz test: Provider enable/disable with random toggles
    function testFuzz_ProviderToggle(uint8 providerIndex, bool enabled) public {
        providerIndex = uint8(bound(providerIndex, 0, 4));
        EnhancedSwapRouter.SwapProvider provider = EnhancedSwapRouter.SwapProvider(providerIndex);
        
        vm.prank(deployer);
        swapRouter.setProviderEnabled(provider, enabled);
        
        assertEq(swapRouter.providerEnabled(provider), enabled, "Provider state should match");
    }

    /// @notice Fuzz test: Balancer pool ID with random values
    function testFuzz_BalancerPoolId(
        address tokenIn,
        address tokenOut,
        bytes32 poolId
    ) public {
        // Avoid zero addresses
        vm.assume(tokenIn != address(0));
        vm.assume(tokenOut != address(0));
        
        vm.prank(deployer);
        swapRouter.setBalancerPoolId(tokenIn, tokenOut, poolId);
        
        assertEq(swapRouter.balancerPoolIds(tokenIn, tokenOut), poolId, "Pool ID should be set");
    }

    /// @notice Fuzz test: Quote requests with random amounts
    function testFuzz_GetQuotes(uint256 amount) public {
        // Bound amount to reasonable range
        amount = bound(amount, 1 wei, 1000000 ether);
        
        // This should not revert (even if quotes are empty)
        try swapRouter.getQuotes(address(usdt), amount) returns (
            EnhancedSwapRouter.SwapProvider[] memory providers,
            uint256[] memory amounts
        ) {
            // Arrays should have same length
            assertEq(providers.length, amounts.length, "Arrays should match length");
        } catch {
            // Revert is acceptable if amount causes issues
            assertTrue(true);
        }
    }

    /// @notice Fuzz test: Bond release with random deposit IDs
    function testFuzz_BondRelease(uint256 depositId, uint256 amount) public {
        // Bound values
        depositId = bound(depositId, 1, type(uint256).max);
        amount = bound(amount, 1 ether, 100 ether);
        
        uint256 bond = bondManager.getRequiredBond(amount);
        
        if (bond <= address(relayer).balance) {
            // Submit claim
            vm.prank(relayer);
            try inbox.submitClaim{value: bond}(depositId, address(0), amount, address(0x3333), "") {
                // Wait for challenge window
                vm.warp(block.timestamp + CHALLENGE_WINDOW + 1);
                
                // Finalize
                challengeManager.finalizeClaim(depositId);
                
                // Try to release
                try bondManager.releaseBond(depositId) {
                    // Verify bond was released
                    (, , , , bool released) = bondManager.bonds(depositId);
                    assertTrue(released, "Bond should be released");
                } catch {
                    // Some reverts are acceptable
                }
            } catch {
                // Claim submission failure is acceptable
            }
        }
    }

    /// @notice Fuzz test: Size-based routing with random amounts
    function testFuzz_SizeBasedRouting(uint256 amount) public {
        // Bound to reasonable range
        amount = bound(amount, 1 wei, 10000000 ether);
        
        // Test that routing logic handles various sizes
        // Small: < 10k, Medium: 10k-100k, Large: > 100k
        // This is tested indirectly through swapToStablecoin
        try swapRouter.getQuotes(address(usdt), amount) {
            // Function should not revert
            assertTrue(true);
        } catch {
            // Revert is acceptable for extreme values
            assertTrue(true);
        }
    }

    /// @notice Fuzz test: Concurrent operations with random parameters
    function testFuzz_ConcurrentOperations(
        uint256 numOperations,
        uint256 baseAmount
    ) public {
        // Bound to reasonable values
        numOperations = bound(numOperations, 1, 50);
        baseAmount = bound(baseAmount, 1 ether, 10 ether);
        
        // Perform multiple operations
        for (uint256 i = 0; i < numOperations; i++) {
            uint256 depositId = i + 1;
            uint256 amount = baseAmount + (i * 1 ether);
            uint256 bond = bondManager.getRequiredBond(amount);
            
            if (bond <= address(relayer).balance) {
                vm.prank(relayer);
                try inbox.submitClaim{value: bond}(
                    depositId,
                    address(0),
                    amount,
                    address(0x3333),
                    ""
                ) {
                    // Operation succeeded
                } catch {
                    // Some failures are acceptable
                }
            }
        }
        
        // System should still be functional
        assertTrue(true);
    }
}

