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

/**
 * @title VaultfireReputationStaking
 * @author Vaultfire Protocol
 * @notice Agent-to-agent vouching with real economic stake. Agents vouch for other
 *         agents by locking ETH. If the vouched agent maintains good standing,
 *         both gain on-chain credibility. If the vouched agent is slashed (via
 *         dispute or penalty), the voucher loses their stake too.
 * @dev Self-contained — no OpenZeppelin imports. Manual ReentrancyGuard included.
 *      Designed to be deployed on Base, Avalanche, Arbitrum, and Polygon.
 */

// ---------------------------------------------------------------------------
//  Minimal interfaces
// ---------------------------------------------------------------------------

interface IIdentityRegistry {
    function isAgentActive(address agent) external view returns (bool);
}

// ---------------------------------------------------------------------------
//  Contract
// ---------------------------------------------------------------------------

contract VaultfireReputationStaking {

    // -----------------------------------------------------------------------
    //  Types
    // -----------------------------------------------------------------------

    enum VouchStatus {
        Active,     // Vouch is live, stake locked
        Withdrawn,  // Voucher reclaimed their stake
        Slashed     // Stake was slashed due to vouched agent penalty
    }

    struct Vouch {
        uint256 id;
        address voucher;       // Agent doing the vouching
        address vouchedAgent;  // Agent being vouched for
        uint256 stakeAmount;   // ETH locked
        uint256 createdAt;
        VouchStatus status;
    }

    struct AgentVouchProfile {
        uint256 totalVouchesReceived;   // How many agents vouch for this agent
        uint256 totalStakeReceived;     // Total ETH staked on this agent
        uint256 totalVouchesGiven;      // How many agents this agent vouches for
        uint256 totalStakeGiven;        // Total ETH this agent has staked on others
        uint256 slashCount;             // Times this agent has been slashed
    }

    // -----------------------------------------------------------------------
    //  Constants
    // -----------------------------------------------------------------------

    /// @notice Minimum vouch stake: 0.001 ETH
    uint256 public constant MIN_VOUCH_STAKE = 0.001 ether;

    /// @notice Minimum lock period before withdrawal: 7 days
    uint256 public constant MIN_LOCK_PERIOD = 7 days;

    // -----------------------------------------------------------------------
    //  Immutables
    // -----------------------------------------------------------------------

    IIdentityRegistry public immutable identityRegistry;
    address public immutable owner;

    // -----------------------------------------------------------------------
    //  State
    // -----------------------------------------------------------------------

    uint256 private _vouchCounter;
    mapping(uint256 => Vouch) private _vouches;
    mapping(address => AgentVouchProfile) public profiles;

    /// @dev voucher => vouchedAgent => vouchId (0 means no active vouch)
    mapping(address => mapping(address => uint256)) public activeVouch;

    /// @dev Agent => list of vouch IDs received
    mapping(address => uint256[]) private _vouchesReceived;

    /// @dev Agent => list of vouch IDs given
    mapping(address => uint256[]) private _vouchesGiven;

    // -----------------------------------------------------------------------
    //  Reentrancy guard
    // -----------------------------------------------------------------------

    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED     = 2;
    uint256 private _reentrancyStatus = _NOT_ENTERED;

    modifier nonReentrant() {
        require(_reentrancyStatus != _ENTERED, "VaultfireReputationStaking: reentrant call");
        _reentrancyStatus = _ENTERED;
        _;
        _reentrancyStatus = _NOT_ENTERED;
    }

    // -----------------------------------------------------------------------
    //  Events
    // -----------------------------------------------------------------------

    event VouchCreated(
        uint256 indexed vouchId,
        address indexed voucher,
        address indexed vouchedAgent,
        uint256 stakeAmount,
        uint256 timestamp
    );

    event VouchWithdrawn(
        uint256 indexed vouchId,
        address indexed voucher,
        address indexed vouchedAgent,
        uint256 stakeReturned,
        uint256 timestamp
    );

    event VouchSlashed(
        uint256 indexed vouchId,
        address indexed voucher,
        address indexed vouchedAgent,
        uint256 stakeSlashed,
        uint256 timestamp
    );

    // -----------------------------------------------------------------------
    //  Constructor
    // -----------------------------------------------------------------------

    constructor(address identityRegistry_) {
        require(identityRegistry_ != address(0), "VaultfireReputationStaking: zero identityRegistry");
        identityRegistry = IIdentityRegistry(identityRegistry_);
        owner = msg.sender;
    }

    // -----------------------------------------------------------------------
    //  Modifiers
    // -----------------------------------------------------------------------

    modifier onlyRegisteredAgent() {
        require(
            identityRegistry.isAgentActive(msg.sender),
            "VaultfireReputationStaking: caller not a registered agent"
        );
        _;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "VaultfireReputationStaking: only owner");
        _;
    }

    // -----------------------------------------------------------------------
    //  Core functions
    // -----------------------------------------------------------------------

    /**
     * @notice Vouch for another agent by staking ETH.
     * @param vouchedAgent The agent being vouched for.
     * @return vouchId The newly created vouch ID.
     */
    function vouch(address vouchedAgent)
        external
        payable
        nonReentrant
        onlyRegisteredAgent
        returns (uint256 vouchId)
    {
        require(vouchedAgent != msg.sender, "VaultfireReputationStaking: cannot vouch for self");
        require(vouchedAgent != address(0), "VaultfireReputationStaking: zero address");
        require(msg.value >= MIN_VOUCH_STAKE, "VaultfireReputationStaking: stake below minimum");
        require(
            identityRegistry.isAgentActive(vouchedAgent),
            "VaultfireReputationStaking: vouched agent not registered"
        );
        require(
            activeVouch[msg.sender][vouchedAgent] == 0,
            "VaultfireReputationStaking: already vouching for this agent"
        );

        unchecked { vouchId = ++_vouchCounter; }

        _vouches[vouchId] = Vouch({
            id: vouchId,
            voucher: msg.sender,
            vouchedAgent: vouchedAgent,
            stakeAmount: msg.value,
            createdAt: block.timestamp,
            status: VouchStatus.Active
        });

        activeVouch[msg.sender][vouchedAgent] = vouchId;
        _vouchesReceived[vouchedAgent].push(vouchId);
        _vouchesGiven[msg.sender].push(vouchId);

        AgentVouchProfile storage receiverProfile = profiles[vouchedAgent];
        receiverProfile.totalVouchesReceived += 1;
        receiverProfile.totalStakeReceived += msg.value;

        AgentVouchProfile storage giverProfile = profiles[msg.sender];
        giverProfile.totalVouchesGiven += 1;
        giverProfile.totalStakeGiven += msg.value;

        emit VouchCreated(vouchId, msg.sender, vouchedAgent, msg.value, block.timestamp);
    }

    /**
     * @notice Withdraw a vouch and reclaim staked ETH.
     * @dev Must wait MIN_LOCK_PERIOD after creation.
     * @param vouchId The vouch to withdraw.
     */
    function withdrawVouch(uint256 vouchId)
        external
        nonReentrant
    {
        Vouch storage v = _vouches[vouchId];
        require(v.voucher == msg.sender, "VaultfireReputationStaking: not your vouch");
        require(v.status == VouchStatus.Active, "VaultfireReputationStaking: vouch not active");
        require(
            block.timestamp >= v.createdAt + MIN_LOCK_PERIOD,
            "VaultfireReputationStaking: lock period not elapsed"
        );

        v.status = VouchStatus.Withdrawn;
        activeVouch[msg.sender][v.vouchedAgent] = 0;

        AgentVouchProfile storage receiverProfile = profiles[v.vouchedAgent];
        receiverProfile.totalVouchesReceived -= 1;
        receiverProfile.totalStakeReceived -= v.stakeAmount;

        AgentVouchProfile storage giverProfile = profiles[msg.sender];
        giverProfile.totalVouchesGiven -= 1;
        giverProfile.totalStakeGiven -= v.stakeAmount;

        uint256 amount = v.stakeAmount;
        (bool ok, ) = msg.sender.call{value: amount}("");
        require(ok, "VaultfireReputationStaking: ETH transfer failed");

        emit VouchWithdrawn(vouchId, msg.sender, v.vouchedAgent, amount, block.timestamp);
    }

    /**
     * @notice Slash all active vouches for a misbehaving agent.
     * @dev Only owner (deployer/governance) can slash. Slashed ETH stays in contract
     *      as protocol reserve.
     * @param agent The agent whose vouches are being slashed.
     */
    function slashAgent(address agent)
        external
        nonReentrant
        onlyOwner
    {
        uint256[] storage received = _vouchesReceived[agent];
        uint256 totalSlashed = 0;

        for (uint256 i = 0; i < received.length; i++) {
            Vouch storage v = _vouches[received[i]];
            if (v.status == VouchStatus.Active) {
                v.status = VouchStatus.Slashed;
                totalSlashed += v.stakeAmount;
                activeVouch[v.voucher][agent] = 0;

                profiles[v.voucher].totalVouchesGiven -= 1;
                profiles[v.voucher].totalStakeGiven -= v.stakeAmount;

                emit VouchSlashed(received[i], v.voucher, agent, v.stakeAmount, block.timestamp);
            }
        }

        AgentVouchProfile storage agentProfile = profiles[agent];
        agentProfile.totalVouchesReceived = 0;
        agentProfile.totalStakeReceived = 0;
        agentProfile.slashCount += 1;
    }

    // -----------------------------------------------------------------------
    //  View functions
    // -----------------------------------------------------------------------

    function getVouch(uint256 vouchId) external view returns (Vouch memory) {
        require(vouchId > 0 && vouchId <= _vouchCounter, "VaultfireReputationStaking: vouch does not exist");
        return _vouches[vouchId];
    }

    function getVouchesReceived(address agent) external view returns (uint256[] memory) {
        return _vouchesReceived[agent];
    }

    function getVouchesGiven(address agent) external view returns (uint256[] memory) {
        return _vouchesGiven[agent];
    }

    function getProfile(address agent)
        external
        view
        returns (
            uint256 totalVouchesReceived,
            uint256 totalStakeReceived,
            uint256 totalVouchesGiven,
            uint256 totalStakeGiven,
            uint256 slashCount
        )
    {
        AgentVouchProfile memory p = profiles[agent];
        return (
            p.totalVouchesReceived,
            p.totalStakeReceived,
            p.totalVouchesGiven,
            p.totalStakeGiven,
            p.slashCount
        );
    }

    function vouchCount() external view returns (uint256) {
        return _vouchCounter;
    }
}
