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

/**
 * @title VaultfireDisputeResolution
 * @author Vaultfire Protocol
 * @notice Decentralized dispute arbitration for the agent economy. When escrow
 *         disputes occur, a panel of high-reputation neutral agents votes on
 *         the outcome. Arbitrators are selected based on Street Cred score and
 *         earn a small fee for honest arbitration.
 * @dev Self-contained — no OpenZeppelin imports. Manual ReentrancyGuard included.
 *      Designed to be deployed on Base, Avalanche, Arbitrum, and Polygon.
 *
 *      Arbitration flow:
 *      1. Dispute is filed (by escrow or directly)
 *      2. Owner assigns a panel of arbitrators (high-rep agents)
 *      3. Arbitrators vote (for claimant or respondent)
 *      4. Majority wins; payout is directed accordingly
 *      5. Arbitrators earn a fee from the dispute stake
 */

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

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

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

contract VaultfireDisputeResolution {

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

    enum DisputeStatus {
        Filed,       // Dispute created
        PanelSet,    // Arbitration panel assigned
        Voting,      // Voting in progress
        Resolved,    // Majority reached, payout sent
        Cancelled    // Cancelled before resolution
    }

    enum Vote {
        None,        // Has not voted
        ForClaimant, // Votes in favor of claimant
        ForRespondent // Votes in favor of respondent
    }

    struct Dispute {
        uint256 id;
        address claimant;       // Party who filed the dispute
        address respondent;     // Party being disputed against
        uint256 stakeAmount;    // ETH at stake in this dispute
        string  description;    // What the dispute is about
        bytes32 evidenceHash;   // keccak256 of evidence URI
        DisputeStatus status;
        uint256 createdAt;
        uint256 resolvedAt;
        address winner;         // Set on resolution
        uint256 votesForClaimant;
        uint256 votesForRespondent;
    }

    struct ArbitratorRecord {
        uint256 totalCasesAssigned;
        uint256 totalCasesVoted;
        uint256 totalFeesEarned;
        bool active;
    }

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

    /// @notice Minimum dispute stake: 0.001 ETH
    uint256 public constant MIN_DISPUTE_STAKE = 0.001 ether;

    /// @notice Arbitrator fee: 5% of dispute stake, split among panel
    uint256 public constant ARBITRATOR_FEE_BPS = 500;

    uint256 private constant BPS_DENOM = 10_000;

    /// @notice Minimum panel size: 3 arbitrators
    uint256 public constant MIN_PANEL_SIZE = 3;

    /// @notice Maximum panel size: 7 arbitrators
    uint256 public constant MAX_PANEL_SIZE = 7;

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

    IIdentityRegistry_DR public immutable identityRegistry;
    address public immutable owner;

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

    uint256 private _disputeCounter;
    mapping(uint256 => Dispute) private _disputes;

    /// @dev disputeId => list of arbitrator addresses
    mapping(uint256 => address[]) private _panels;

    /// @dev disputeId => arbitrator => vote
    mapping(uint256 => mapping(address => Vote)) private _votes;

    /// @dev Arbitrator address => record
    mapping(address => ArbitratorRecord) public arbitrators;

    /// @dev Global stats
    uint256 public totalDisputesFiled;
    uint256 public totalDisputesResolved;
    uint256 public totalStakeDisputed;

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

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

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

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

    event DisputeFiled(
        uint256 indexed disputeId,
        address indexed claimant,
        address indexed respondent,
        uint256 stakeAmount,
        string description,
        uint256 timestamp
    );

    event PanelAssigned(
        uint256 indexed disputeId,
        address[] panel,
        uint256 timestamp
    );

    event VoteCast(
        uint256 indexed disputeId,
        address indexed arbitrator,
        Vote vote,
        uint256 timestamp
    );

    event DisputeResolved(
        uint256 indexed disputeId,
        address indexed winner,
        uint256 payout,
        uint256 arbitratorFeePerMember,
        uint256 timestamp
    );

    event DisputeCancelled(
        uint256 indexed disputeId,
        uint256 timestamp
    );

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

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

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

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

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

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

    /**
     * @notice File a dispute against another agent.
     * @param respondent The agent being disputed against.
     * @param description What the dispute is about.
     * @param evidenceHash keccak256 of off-chain evidence URI.
     * @return disputeId The newly created dispute ID.
     */
    function fileDispute(
        address respondent,
        string calldata description,
        bytes32 evidenceHash
    )
        external
        payable
        nonReentrant
        onlyRegisteredAgent
        returns (uint256 disputeId)
    {
        require(respondent != address(0), "VaultfireDisputeResolution: zero respondent");
        require(respondent != msg.sender, "VaultfireDisputeResolution: cannot dispute self");
        require(msg.value >= MIN_DISPUTE_STAKE, "VaultfireDisputeResolution: stake below minimum");
        require(bytes(description).length > 0, "VaultfireDisputeResolution: empty description");
        require(evidenceHash != bytes32(0), "VaultfireDisputeResolution: evidence hash required");

        unchecked { disputeId = ++_disputeCounter; }

        _disputes[disputeId] = Dispute({
            id: disputeId,
            claimant: msg.sender,
            respondent: respondent,
            stakeAmount: msg.value,
            description: description,
            evidenceHash: evidenceHash,
            status: DisputeStatus.Filed,
            createdAt: block.timestamp,
            resolvedAt: 0,
            winner: address(0),
            votesForClaimant: 0,
            votesForRespondent: 0
        });

        totalDisputesFiled += 1;
        totalStakeDisputed += msg.value;

        emit DisputeFiled(disputeId, msg.sender, respondent, msg.value, description, block.timestamp);
    }

    /**
     * @notice Assign an arbitration panel to a dispute.
     * @dev Only owner (governance) can assign panels. All panel members must be
     *      registered agents.
     * @param disputeId The dispute to assign a panel to.
     * @param panel Array of arbitrator addresses.
     */
    function assignPanel(uint256 disputeId, address[] calldata panel)
        external
        nonReentrant
        onlyOwner
    {
        require(
            disputeId > 0 && disputeId <= _disputeCounter,
            "VaultfireDisputeResolution: dispute does not exist"
        );

        Dispute storage d = _disputes[disputeId];
        require(d.status == DisputeStatus.Filed, "VaultfireDisputeResolution: dispute not in Filed state");
        require(
            panel.length >= MIN_PANEL_SIZE && panel.length <= MAX_PANEL_SIZE,
            "VaultfireDisputeResolution: invalid panel size"
        );
        // Panel size must be odd for clear majority
        require(panel.length % 2 == 1, "VaultfireDisputeResolution: panel size must be odd");

        for (uint256 i = 0; i < panel.length; i++) {
            require(
                identityRegistry.isAgentActive(panel[i]),
                "VaultfireDisputeResolution: panel member not registered"
            );
            require(
                panel[i] != d.claimant && panel[i] != d.respondent,
                "VaultfireDisputeResolution: party cannot be arbitrator"
            );
            // Check for duplicates
            for (uint256 j = 0; j < i; j++) {
                require(panel[i] != panel[j], "VaultfireDisputeResolution: duplicate panel member");
            }

            arbitrators[panel[i]].totalCasesAssigned += 1;
            if (!arbitrators[panel[i]].active) {
                arbitrators[panel[i]].active = true;
            }
        }

        _panels[disputeId] = panel;
        d.status = DisputeStatus.PanelSet;

        emit PanelAssigned(disputeId, panel, block.timestamp);
    }

    /**
     * @notice Cast a vote on a dispute as an arbitrator.
     * @param disputeId The dispute to vote on.
     * @param vote_ Vote.ForClaimant or Vote.ForRespondent.
     */
    function castVote(uint256 disputeId, Vote vote_)
        external
        nonReentrant
    {
        require(
            disputeId > 0 && disputeId <= _disputeCounter,
            "VaultfireDisputeResolution: dispute does not exist"
        );
        require(
            vote_ == Vote.ForClaimant || vote_ == Vote.ForRespondent,
            "VaultfireDisputeResolution: invalid vote"
        );

        Dispute storage d = _disputes[disputeId];
        require(
            d.status == DisputeStatus.PanelSet || d.status == DisputeStatus.Voting,
            "VaultfireDisputeResolution: not in voting phase"
        );

        // Verify caller is on the panel
        address[] storage panel = _panels[disputeId];
        bool onPanel = false;
        for (uint256 i = 0; i < panel.length; i++) {
            if (panel[i] == msg.sender) {
                onPanel = true;
                break;
            }
        }
        require(onPanel, "VaultfireDisputeResolution: not on arbitration panel");

        require(
            _votes[disputeId][msg.sender] == Vote.None,
            "VaultfireDisputeResolution: already voted"
        );

        _votes[disputeId][msg.sender] = vote_;
        arbitrators[msg.sender].totalCasesVoted += 1;

        if (vote_ == Vote.ForClaimant) {
            d.votesForClaimant += 1;
        } else {
            d.votesForRespondent += 1;
        }

        // Move to Voting status on first vote
        if (d.status == DisputeStatus.PanelSet) {
            d.status = DisputeStatus.Voting;
        }

        emit VoteCast(disputeId, msg.sender, vote_, block.timestamp);

        // Check if all votes are in — auto-resolve if majority reached
        uint256 totalVotes = d.votesForClaimant + d.votesForRespondent;
        if (totalVotes == panel.length) {
            _resolveDispute(disputeId);
        }
    }

    // -----------------------------------------------------------------------
    //  Internal
    // -----------------------------------------------------------------------

    function _resolveDispute(uint256 disputeId) internal {
        Dispute storage d = _disputes[disputeId];
        address[] storage panel = _panels[disputeId];

        d.status = DisputeStatus.Resolved;
        d.resolvedAt = block.timestamp;

        // Determine winner
        address winner;
        if (d.votesForClaimant > d.votesForRespondent) {
            winner = d.claimant;
        } else {
            winner = d.respondent;
        }
        d.winner = winner;

        // Calculate fees and payout
        uint256 totalFee = (d.stakeAmount * ARBITRATOR_FEE_BPS) / BPS_DENOM;
        uint256 feePerArbitrator = totalFee / panel.length;
        uint256 payout = d.stakeAmount - totalFee;

        // Pay winner
        (bool ok, ) = winner.call{value: payout}("");
        require(ok, "VaultfireDisputeResolution: winner payout failed");

        // Pay arbitrators
        for (uint256 i = 0; i < panel.length; i++) {
            arbitrators[panel[i]].totalFeesEarned += feePerArbitrator;
            (bool ok2, ) = panel[i].call{value: feePerArbitrator}("");
            require(ok2, "VaultfireDisputeResolution: arbitrator fee failed");
        }

        totalDisputesResolved += 1;

        emit DisputeResolved(disputeId, winner, payout, feePerArbitrator, block.timestamp);
    }

    /**
     * @notice Cancel a dispute before panel assignment. Stake returned to claimant.
     * @param disputeId The dispute to cancel.
     */
    function cancelDispute(uint256 disputeId)
        external
        nonReentrant
    {
        require(
            disputeId > 0 && disputeId <= _disputeCounter,
            "VaultfireDisputeResolution: dispute does not exist"
        );

        Dispute storage d = _disputes[disputeId];
        require(d.claimant == msg.sender, "VaultfireDisputeResolution: only claimant can cancel");
        require(d.status == DisputeStatus.Filed, "VaultfireDisputeResolution: can only cancel before panel");

        d.status = DisputeStatus.Cancelled;

        (bool ok, ) = d.claimant.call{value: d.stakeAmount}("");
        require(ok, "VaultfireDisputeResolution: refund failed");

        emit DisputeCancelled(disputeId, block.timestamp);
    }

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

    function getDispute(uint256 disputeId) external view returns (Dispute memory) {
        require(
            disputeId > 0 && disputeId <= _disputeCounter,
            "VaultfireDisputeResolution: dispute does not exist"
        );
        return _disputes[disputeId];
    }

    function getPanel(uint256 disputeId) external view returns (address[] memory) {
        return _panels[disputeId];
    }

    function getVote(uint256 disputeId, address arbitrator) external view returns (Vote) {
        return _votes[disputeId][arbitrator];
    }

    function getArbitratorRecord(address arbitrator)
        external
        view
        returns (
            uint256 totalCasesAssigned,
            uint256 totalCasesVoted,
            uint256 totalFeesEarned,
            bool active
        )
    {
        ArbitratorRecord memory r = arbitrators[arbitrator];
        return (r.totalCasesAssigned, r.totalCasesVoted, r.totalFeesEarned, r.active);
    }

    function getDisputeStats()
        external
        view
        returns (
            uint256 filed,
            uint256 resolved,
            uint256 totalStake
        )
    {
        return (totalDisputesFiled, totalDisputesResolved, totalStakeDisputed);
    }

    function disputeCount() external view returns (uint256) {
        return _disputeCounter;
    }
}
