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

import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title BondManager
 * @notice Manages bonds for trustless bridge claims with dynamic sizing and slashing
 * @dev Bonds are posted in ETH. Slashed bonds split 50% to challenger, 50% burned (sent to address(0)).
 */
contract BondManager is ReentrancyGuard {
    // Bond configuration
    uint256 public immutable bondMultiplier; // Basis points (11000 = 110%)
    uint256 public immutable minBond; // Minimum bond amount in wei
    
    // Bond tracking
    struct Bond {
        address relayer;      // Slot 0 (20 bytes) + 12 bytes padding
        uint256 amount;       // Slot 1
        uint256 depositId;   // Slot 2
        bool slashed;        // Slot 3 (1 byte) + 31 bytes padding
        bool released;       // Slot 4 (1 byte) + 31 bytes padding
        // Note: Could pack slashed and released in same slot, but keeping separate for clarity
    }
    
    mapping(uint256 => Bond) public bonds; // depositId => Bond
    mapping(address => uint256) public totalBonds; // relayer => total bonded amount

    /// @notice Total ETH held in active (non-released, non-slashed) bonds. Used for value-conservation invariant.
    uint256 public totalEthHeld;

    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
    );

    error ZeroDepositId();
    error ZeroRelayer();
    error InsufficientBond();
    error BondNotFound();
    error BondAlreadySlashed();
    error BondAlreadyReleased();
    error BondNotReleased();

    /**
     * @notice Constructor sets bond parameters
     * @param _bondMultiplier Bond multiplier in basis points (11000 = 110% = 1.1x)
     * @param _minBond Minimum bond amount in wei
     */
    constructor(uint256 _bondMultiplier, uint256 _minBond) {
        require(_bondMultiplier >= 10000, "BondManager: multiplier must be >= 100%");
        require(_minBond > 0, "BondManager: minBond must be > 0");
        bondMultiplier = _bondMultiplier;
        minBond = _minBond;
    }

    /**
     * @notice Post bond for a claim
     * @param depositId Deposit ID from source chain
     * @param depositAmount Amount of the deposit (used to calculate bond size)
     * @param relayer Address of the relayer posting the bond (can be different from msg.sender if called by InboxETH)
     * @return bondAmount The bond amount that was posted
     */
    function postBond(
        uint256 depositId,
        uint256 depositAmount,
        address relayer
    ) external payable nonReentrant returns (uint256) {
        if (depositId == 0) revert ZeroDepositId();
        if (relayer == address(0)) revert ZeroRelayer();
        
        // Check if bond already exists
        require(bonds[depositId].relayer == address(0), "BondManager: bond already posted");

        // Calculate required bond amount
        uint256 requiredBond = getRequiredBond(depositAmount);
        if (msg.value < requiredBond) revert InsufficientBond();

        // Store bond information
        bonds[depositId] = Bond({
            relayer: relayer,
            amount: msg.value,
            depositId: depositId,
            slashed: false,
            released: false
        });

        totalBonds[relayer] += msg.value;
        totalEthHeld += msg.value;

        emit BondPosted(depositId, msg.sender, msg.value);

        return msg.value;
    }

    /**
     * @notice Slash bond due to fraudulent claim
     * @param depositId Deposit ID associated with the bond
     * @param challenger Address of the challenger proving fraud
     * @return challengerReward Amount sent to challenger
     * @return burnedAmount Amount burned (sent to address(0))
     */
    function slashBond(
        uint256 depositId,
        address challenger
    ) external nonReentrant returns (uint256 challengerReward, uint256 burnedAmount) {
        Bond storage bond = bonds[depositId];
        
        if (bond.relayer == address(0)) revert BondNotFound();
        if (bond.slashed) revert BondAlreadySlashed();
        if (challenger == address(0)) revert ZeroRelayer();

        // Mark bond as slashed
        bond.slashed = true;
        
        uint256 bondAmount = bond.amount;
        
        // Update relayer's total bonds and total ETH held
        totalBonds[bond.relayer] -= bondAmount;
        totalEthHeld -= bondAmount;

        // Split bond: 50% to challenger, 50% burned
        challengerReward = bondAmount / 2;
        burnedAmount = bondAmount - challengerReward; // Handle odd amounts

        // Transfer to challenger
        (bool success1, ) = payable(challenger).call{value: challengerReward}("");
        require(success1, "BondManager: challenger transfer failed");

        // Burn remaining amount (send to address(0))
        // Note: In practice, sending ETH to address(0) doesn't actually burn it,
        // but it makes the funds inaccessible. For true burning, consider using a burn mechanism.
        (bool success2, ) = payable(address(0)).call{value: burnedAmount}("");
        require(success2, "BondManager: burn transfer failed");

        emit BondSlashed(
            depositId,
            bond.relayer,
            challenger,
            bondAmount,
            challengerReward,
            burnedAmount
        );

        return (challengerReward, burnedAmount);
    }

    /**
     * @notice Release bond after successful claim finalization
     * @param depositId Deposit ID associated with the bond
     * @return bondAmount Amount returned to relayer
     */
    function releaseBond(
        uint256 depositId
    ) external nonReentrant returns (uint256) {
        Bond storage bond = bonds[depositId];
        
        if (bond.relayer == address(0)) revert BondNotFound();
        if (bond.slashed) revert BondAlreadySlashed();
        if (bond.released) revert BondAlreadyReleased();

        // Mark bond as released
        bond.released = true;
        
        uint256 bondAmount = bond.amount;
        address relayer = bond.relayer; // Cache to save gas
        
        // Update relayer's total bonds
        totalBonds[relayer] -= bondAmount;

        // Transfer bond back to relayer
        (bool success, ) = payable(relayer).call{value: bondAmount}("");
        require(success, "BondManager: release transfer failed");

        emit BondReleased(depositId, relayer, bondAmount);

        return bondAmount;
    }

    /**
     * @notice Release multiple bonds in batch (gas optimization)
     * @param depositIds Array of deposit IDs to release bonds for
     * @return totalReleased Total amount released
     */
    function releaseBondsBatch(uint256[] calldata depositIds) external nonReentrant returns (uint256 totalReleased) {
        uint256 length = depositIds.length;
        require(length > 0, "BondManager: empty array");
        require(length <= 50, "BondManager: batch too large"); // Prevent gas limit issues

        for (uint256 i = 0; i < length; i++) {
            uint256 depositId = depositIds[i];
            if (depositId == 0) continue; // Skip zero IDs

            Bond storage bond = bonds[depositId];
            if (bond.relayer == address(0)) continue; // Skip non-existent bonds
            if (bond.slashed) continue; // Skip slashed bonds
            if (bond.released) continue; // Skip already released

            bond.released = true;
            uint256 bondAmount = bond.amount;
            address relayer = bond.relayer; // Cache to save gas
            
            totalBonds[relayer] -= bondAmount;
            totalEthHeld -= bondAmount;
            totalReleased += bondAmount;

            (bool success, ) = payable(relayer).call{value: bondAmount}("");
            require(success, "BondManager: release transfer failed");

            emit BondReleased(depositId, relayer, bondAmount);
        }

        return totalReleased;
    }

    /**
     * @notice Calculate required bond amount for a deposit
     * @param depositAmount Amount of the deposit
     * @return requiredBond Minimum bond amount required
     */
    function getRequiredBond(uint256 depositAmount) public view returns (uint256) {
        uint256 calculatedBond = (depositAmount * bondMultiplier) / 10000;
        return calculatedBond > minBond ? calculatedBond : minBond;
    }

    /**
     * @notice Get bond information for a deposit
     * @param depositId Deposit ID to check
     * @return relayer Address that posted the bond
     * @return amount Bond amount
     * @return slashed Whether bond has been slashed
     * @return released Whether bond has been released
     */
    function getBond(
        uint256 depositId
    ) external view returns (
        address relayer,
        uint256 amount,
        bool slashed,
        bool released
    ) {
        Bond memory bond = bonds[depositId];
        return (bond.relayer, bond.amount, bond.slashed, bond.released);
    }

    /**
     * @notice Get total bonds posted by a relayer
     * @param relayer Address to check
     * @return Total amount of bonds posted
     */
    function getTotalBonds(address relayer) external view returns (uint256) {
        return totalBonds[relayer];
    }

    // Allow contract to receive ETH
    receive() external payable {}
}
