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

import "forge-std/Test.sol";
import "../../../contracts/bridge/trustless/Lockbox138.sol";
import "../../../contracts/bridge/trustless/BondManager.sol";
import "../../../contracts/bridge/trustless/ChallengeManager.sol";
import "../../../contracts/bridge/trustless/LiquidityPoolETH.sol";
import "../../../contracts/bridge/trustless/InboxETH.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockWETH is ERC20 {
    constructor() ERC20("Wrapped Ether", "WETH") {}

    function deposit() external payable {
        _mint(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) external {
        _burn(msg.sender, amount);
        payable(msg.sender).transfer(amount);
    }
}

contract EndToEndTest is Test {
    // ChainID 138 contracts
    Lockbox138 public lockbox;

    // Ethereum contracts
    BondManager public bondManager;
    ChallengeManager public challengeManager;
    LiquidityPoolETH public liquidityPool;
    InboxETH public inbox;
    MockWETH public weth;

    // Test addresses
    address public user = address(0x1);
    address public relayer = address(0x2);
    address public challenger = address(0x3);
    address public lp = address(0x4);
    address public recipient = address(0x5);

    uint256 constant BOND_MULTIPLIER = 11000; // 110%
    uint256 constant MIN_BOND = 1 ether;
    uint256 constant CHALLENGE_WINDOW = 30 minutes;
    uint256 constant LP_FEE_BPS = 5; // 0.05%
    uint256 constant MIN_LIQUIDITY_RATIO_BPS = 11000; // 110%

    function setUp() public {
        // Deploy WETH
        weth = new MockWETH();

        // Deploy Ethereum contracts
        bondManager = new BondManager(BOND_MULTIPLIER, MIN_BOND);
        challengeManager = new ChallengeManager(address(bondManager), CHALLENGE_WINDOW);
        liquidityPool = new LiquidityPoolETH(address(weth), LP_FEE_BPS, MIN_LIQUIDITY_RATIO_BPS);
        inbox = new InboxETH(address(bondManager), address(challengeManager), address(liquidityPool));

        // Authorize inbox to manage pending claims
        liquidityPool.authorizeRelease(address(inbox));

        // Deploy ChainID 138 contract
        lockbox = new Lockbox138();

        // Fund addresses
        vm.deal(user, 100 ether);
        vm.deal(relayer, 100 ether);
        vm.deal(challenger, 100 ether);
        vm.deal(lp, 1000 ether);
        vm.deal(recipient, 10 ether);
        
        // Set initial timestamp to avoid cooldown issues with uninitialized lastClaimTime
        vm.warp(1000);
    }

    function testHappyPath_DepositClaimFinalize() public {
        uint256 depositAmount = 10 ether;
        bytes32 nonce = keccak256("test-nonce");
        uint256 depositId;

        // Step 1: User deposits on ChainID 138
        vm.prank(user);
        depositId = lockbox.depositNative{value: depositAmount}(recipient, nonce);

        // Step 2: Provide liquidity on Ethereum
        vm.prank(lp);
        liquidityPool.provideLiquidity{value: 100 ether}(LiquidityPoolETH.AssetType.ETH);

        // Step 3: Relayer submits claim on Ethereum
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);
        // Advance time to ensure no cooldown issues (first claim doesn't need this, but safe)
        vm.warp(block.timestamp + 1);
        vm.prank(relayer);
        inbox.submitClaim{value: requiredBond}(
            depositId,
            address(0), // ETH
            depositAmount,
            recipient,
            ""
        );

        // Step 4: Wait for challenge window to expire
        vm.warp(block.timestamp + CHALLENGE_WINDOW + 1);

        // Step 5: Finalize claim
        challengeManager.finalizeClaim(depositId);

        // Verify claim is finalized
        ChallengeManager.Claim memory claim = challengeManager.getClaim(depositId);
        assertTrue(claim.finalized);
        assertFalse(claim.challenged);

        // Step 6: Release bond (would be done by coordinator in production)
        uint256 relayerBalanceBefore = relayer.balance;
        bondManager.releaseBond(depositId);

        // Verify bond released
        assertEq(relayer.balance, relayerBalanceBefore + requiredBond); // Bond returned
    }

    function testChallenge_FraudProof() public {
        uint256 depositAmount = 10 ether;
        bytes32 nonce = keccak256("test-nonce-challenge");
        uint256 depositId;

        // Step 1: User deposits
        vm.prank(user);
        depositId = lockbox.depositNative{value: depositAmount}(recipient, nonce);

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

        // Step 3: Relayer submits fraudulent claim (wrong amount)
        uint256 fraudulentAmount = depositAmount * 2; // Claim double the amount
        uint256 requiredBond = bondManager.getRequiredBond(fraudulentAmount);
        vm.warp(block.timestamp + 1); // Advance time
        vm.prank(relayer);
        inbox.submitClaim{value: requiredBond}(
            depositId,
            address(0),
            fraudulentAmount, // Wrong amount
            recipient,
            ""
        );

        // Step 4: Challenger attempts to challenge the claim (within challenge window)
        // Note: The fraud proof is invalid, so the challenge should fail
        bytes memory fraudProof = abi.encode("fraud-proof-data");
        vm.prank(challenger);
        vm.expectRevert(); // Expect revert (InvalidFraudProof)
        challengeManager.challengeClaim(
            depositId,
            ChallengeManager.FraudProofType.IncorrectAmount,
            fraudProof
        );
        
        // Since the proof is invalid, the challenge fails and bond is not slashed
        // This test verifies that invalid proofs are rejected
    }

    function testLiquidityPool_WithdrawBlocked() public {
        uint256 depositAmount = 10 ether;
        bytes32 nonce = keccak256("test-nonce-lp");
        uint256 depositId;

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

        // Submit claim
        vm.prank(user);
        depositId = lockbox.depositNative{value: depositAmount}(recipient, nonce);

        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);
        vm.warp(block.timestamp + 1); // Advance time
        vm.prank(relayer);
        inbox.submitClaim{value: requiredBond}(
            depositId,
            address(0),
            depositAmount,
            recipient,
            ""
        );

        // Check pool stats before withdrawal
        (, uint256 pendingBefore, ) = liquidityPool.getPoolStats(LiquidityPoolETH.AssetType.ETH);
        // With 100 ETH total, 10 ETH pending, we need at least 11 ETH available (110% of 10)
        // So we can only withdraw up to 89 ETH (100 - 11)
        // Try to withdraw 90 ETH (should fail)
        vm.prank(lp);
        vm.expectRevert(LiquidityPoolETH.WithdrawalBlockedByLiquidityRatio.selector);
        liquidityPool.withdrawLiquidity(90 ether, LiquidityPoolETH.AssetType.ETH);

        // Can withdraw smaller amount that maintains ratio (e.g., 10 ether)
        vm.prank(lp);
        liquidityPool.withdrawLiquidity(10 ether, LiquidityPoolETH.AssetType.ETH);
    }

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

        // Multiple deposits
        for (uint256 i = 0; i < 5; i++) {
            uint256 depositAmount = 10 ether;
            bytes32 nonce = keccak256(abi.encodePacked("nonce-", i));
            uint256 depositId;

            vm.prank(user);
            depositId = lockbox.depositNative{value: depositAmount}(recipient, nonce);

            uint256 requiredBond = bondManager.getRequiredBond(depositAmount);
            vm.deal(relayer, 100 ether);
            // Advance time to respect cooldown between claims
            vm.warp(block.timestamp + 61 seconds);
            vm.prank(relayer);
            inbox.submitClaim{value: requiredBond}(
                depositId,
                address(0),
                depositAmount,
                recipient,
                ""
            );
        }

        // All claims should be pending (check via getPoolStats)
        (uint256 total, uint256 pending, ) = liquidityPool.getPoolStats(LiquidityPoolETH.AssetType.ETH);
        assertEq(pending, 50 ether);
    }
}
