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

import "forge-std/Test.sol";
import "../../../contracts/bridge/trustless/BondManager.sol";

contract BondManagerTest is Test {
    BondManager public bondManager;
    uint256 constant BOND_MULTIPLIER = 11000; // 110%
    uint256 constant MIN_BOND = 1 ether;

    address public relayer1 = address(0x1);
    address public relayer2 = address(0x2);
    address public challenger = address(0x3);

    event BondPosted(uint256 indexed depositId, address indexed relayer, uint256 bondAmount);
    event BondSlashed(
        uint256 indexed depositId,
        address indexed relayer,
        address indexed challenger,
        uint256 bondAmount,
        uint256 challengerReward,
        uint256 burnedAmount
    );
    event BondReleased(uint256 indexed depositId, address indexed relayer, uint256 bondAmount);

    function setUp() public {
        bondManager = new BondManager(BOND_MULTIPLIER, MIN_BOND);
        vm.deal(relayer1, 100 ether);
        vm.deal(relayer2, 100 ether);
        vm.deal(challenger, 100 ether);
    }

    function testPostBond() public {
        uint256 depositId = 1;
        uint256 depositAmount = 10 ether;
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);

        vm.prank(relayer1);
        vm.expectEmit(true, true, false, true);
        emit BondPosted(depositId, relayer1, requiredBond);
        bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1);

        (address relayer, uint256 amount, bool slashed, bool released) = bondManager.getBond(depositId);
        assertEq(relayer, relayer1);
        assertEq(amount, requiredBond);
        assertFalse(slashed);
        assertFalse(released);
        assertEq(bondManager.getTotalBonds(relayer1), requiredBond);
    }

    function testPostBondMinimumBond() public {
        uint256 depositId = 2;
        uint256 depositAmount = 0.5 ether; // Less than min bond

        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);
        assertEq(requiredBond, MIN_BOND); // Should use minimum bond

        vm.prank(relayer1);
        bondManager.postBond{value: MIN_BOND}(depositId, depositAmount, relayer1);
    }

    function testPostBondInsufficientBond() public {
        uint256 depositId = 3;
        uint256 depositAmount = 10 ether;
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);

        vm.prank(relayer1);
        vm.expectRevert(BondManager.InsufficientBond.selector);
        bondManager.postBond{value: requiredBond - 1}(depositId, depositAmount, relayer1);
    }

    function testSlashBond() public {
        uint256 depositId = 4;
        uint256 depositAmount = 10 ether;
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);

        vm.prank(relayer1);
        bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1);

        uint256 challengerBalanceBefore = challenger.balance;

        vm.prank(address(this)); // This contract calls slashBond (in practice, ChallengeManager would call it)
        vm.expectEmit(true, true, true, true);
        emit BondSlashed(depositId, relayer1, challenger, requiredBond, requiredBond / 2, requiredBond - requiredBond / 2);
        bondManager.slashBond(depositId, challenger);

        uint256 challengerReward = requiredBond / 2;
        assertEq(challenger.balance - challengerBalanceBefore, challengerReward);
        assertEq(bondManager.getTotalBonds(relayer1), 0);

        (address relayer, uint256 amount, bool slashed, bool released) = bondManager.getBond(depositId);
        assertTrue(slashed);
        assertFalse(released);
    }

    function testReleaseBond() public {
        uint256 depositId = 5;
        uint256 depositAmount = 10 ether;
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);

        vm.prank(relayer1);
        bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1);

        uint256 relayerBalanceBefore = relayer1.balance;

        vm.prank(address(this)); // This contract calls releaseBond (in practice, InboxETH would call it)
        vm.expectEmit(true, true, false, true);
        emit BondReleased(depositId, relayer1, requiredBond);
        bondManager.releaseBond(depositId);

        assertEq(relayer1.balance - relayerBalanceBefore, requiredBond);
        assertEq(bondManager.getTotalBonds(relayer1), 0);

        (address relayer, uint256 amount, bool slashed, bool released) = bondManager.getBond(depositId);
        assertFalse(slashed);
        assertTrue(released);
    }

    function testGetRequiredBond() public {
        // Test with deposit amount that requires multiplier
        uint256 depositAmount = 10 ether;
        uint256 expectedBond = (depositAmount * BOND_MULTIPLIER) / 10000;
        assertEq(bondManager.getRequiredBond(depositAmount), expectedBond);

        // Test with deposit amount below minimum
        depositAmount = 0.5 ether;
        assertEq(bondManager.getRequiredBond(depositAmount), MIN_BOND);
    }

    function testSlashBondNotFound() public {
        vm.expectRevert(BondManager.BondNotFound.selector);
        bondManager.slashBond(999, challenger);
    }

    function testReleaseBondNotFound() public {
        vm.expectRevert(BondManager.BondNotFound.selector);
        bondManager.releaseBond(999);
    }

    function testSlashBondAlreadySlashed() public {
        uint256 depositId = 6;
        uint256 depositAmount = 10 ether;
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);

        vm.prank(relayer1);
        bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1);

        bondManager.slashBond(depositId, challenger);

        vm.expectRevert(BondManager.BondAlreadySlashed.selector);
        bondManager.slashBond(depositId, challenger);
    }

    function testReleaseBondAlreadyReleased() public {
        uint256 depositId = 7;
        uint256 depositAmount = 10 ether;
        uint256 requiredBond = bondManager.getRequiredBond(depositAmount);

        vm.prank(relayer1);
        bondManager.postBond{value: requiredBond}(depositId, depositAmount, relayer1);

        bondManager.releaseBond(depositId);

        vm.expectRevert(BondManager.BondAlreadyReleased.selector);
        bondManager.releaseBond(depositId);
    }
}
