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

/**
 * @title VaultfireBondInsurancePool
 * @author Vaultfire Protocol
 * @notice Shared insurance pool for bond risk management. Agents contribute ETH to
 *         a collective pool. If a bonded agent fails or a task goes wrong, the pool
 *         covers the loss up to a per-claim cap. Contributors earn an "Insured" badge
 *         on their trust profile, making them more attractive for high-value tasks.
 * @dev Self-contained — no OpenZeppelin imports. Manual ReentrancyGuard included.
 *      Designed to be deployed on Base, Avalanche, Arbitrum, and Polygon.
 */

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

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

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

contract VaultfireBondInsurancePool {

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

    enum ClaimStatus {
        Pending,    // Claim submitted, awaiting review
        Approved,   // Claim approved, payout sent
        Denied      // Claim denied
    }

    struct Contribution {
        address contributor;
        uint256 amount;
        uint256 timestamp;
    }

    struct InsuranceClaim {
        uint256 id;
        address claimant;       // Agent filing the claim
        uint256 amount;         // Requested payout amount
        string  reason;         // Why the claim was filed
        bytes32 evidenceHash;   // keccak256 of off-chain evidence
        ClaimStatus status;
        uint256 payout;         // Actual payout (may differ from requested)
        uint256 createdAt;
        uint256 resolvedAt;
    }

    struct ContributorProfile {
        uint256 totalContributed;
        uint256 contributionCount;
        bool insured;               // True if totalContributed >= MIN_FOR_INSURED
        uint256 firstContributionAt;
    }

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

    /// @notice Minimum contribution: 0.001 ETH
    uint256 public constant MIN_CONTRIBUTION = 0.001 ether;

    /// @notice Minimum total contributed to earn "Insured" badge: 0.01 ETH
    uint256 public constant MIN_FOR_INSURED = 0.01 ether;

    /// @notice Maximum payout per claim: 10% of pool balance at time of claim
    uint256 public constant MAX_CLAIM_PERCENT = 1000; // 10% in basis points

    uint256 private constant BPS_DENOM = 10_000;

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

    IIdentityRegistry_BIP public immutable identityRegistry;
    address public immutable owner;

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

    uint256 public totalPoolBalance;
    uint256 public totalContributors;
    uint256 public totalClaimsCount;
    uint256 public totalPaidOut;

    uint256 private _claimCounter;
    mapping(uint256 => InsuranceClaim) private _claims;
    mapping(address => ContributorProfile) public contributors;
    mapping(address => uint256[]) private _contributorHistory;

    Contribution[] private _allContributions;

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

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

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

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

    event ContributionMade(
        address indexed contributor,
        uint256 amount,
        uint256 totalContributed,
        bool insuredStatus,
        uint256 timestamp
    );

    event ClaimSubmitted(
        uint256 indexed claimId,
        address indexed claimant,
        uint256 requestedAmount,
        string reason,
        uint256 timestamp
    );

    event ClaimResolved(
        uint256 indexed claimId,
        address indexed claimant,
        ClaimStatus status,
        uint256 payout,
        uint256 timestamp
    );

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

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

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

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

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

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

    /**
     * @notice Contribute ETH to the insurance pool.
     * @dev Caller must be a registered agent. Contributing >= MIN_FOR_INSURED total
     *      earns the "Insured" badge.
     */
    function contribute()
        external
        payable
        nonReentrant
        onlyRegisteredAgent
    {
        require(msg.value >= MIN_CONTRIBUTION, "VaultfireBondInsurancePool: below minimum");

        ContributorProfile storage profile = contributors[msg.sender];

        if (profile.contributionCount == 0) {
            totalContributors += 1;
            profile.firstContributionAt = block.timestamp;
        }

        profile.totalContributed += msg.value;
        profile.contributionCount += 1;

        // Check if contributor now qualifies for Insured badge
        if (!profile.insured && profile.totalContributed >= MIN_FOR_INSURED) {
            profile.insured = true;
        }

        totalPoolBalance += msg.value;

        _allContributions.push(Contribution({
            contributor: msg.sender,
            amount: msg.value,
            timestamp: block.timestamp
        }));

        emit ContributionMade(
            msg.sender,
            msg.value,
            profile.totalContributed,
            profile.insured,
            block.timestamp
        );
    }

    /**
     * @notice Submit an insurance claim against the pool.
     * @param amount Requested payout amount.
     * @param reason Why the claim is being filed.
     * @param evidenceHash keccak256 of off-chain evidence URI.
     * @return claimId The newly created claim ID.
     */
    function submitClaim(
        uint256 amount,
        string calldata reason,
        bytes32 evidenceHash
    )
        external
        nonReentrant
        onlyRegisteredAgent
        returns (uint256 claimId)
    {
        require(amount > 0, "VaultfireBondInsurancePool: zero claim amount");
        require(bytes(reason).length > 0, "VaultfireBondInsurancePool: empty reason");
        require(evidenceHash != bytes32(0), "VaultfireBondInsurancePool: evidence hash required");

        // Claim cannot exceed MAX_CLAIM_PERCENT of current pool
        uint256 maxPayout = (totalPoolBalance * MAX_CLAIM_PERCENT) / BPS_DENOM;
        require(amount <= maxPayout, "VaultfireBondInsurancePool: claim exceeds max payout");

        // Must be an insured contributor to file claims
        require(
            contributors[msg.sender].insured,
            "VaultfireBondInsurancePool: must be insured to file claims"
        );

        unchecked { claimId = ++_claimCounter; }

        _claims[claimId] = InsuranceClaim({
            id: claimId,
            claimant: msg.sender,
            amount: amount,
            reason: reason,
            evidenceHash: evidenceHash,
            status: ClaimStatus.Pending,
            payout: 0,
            createdAt: block.timestamp,
            resolvedAt: 0
        });

        totalClaimsCount += 1;

        emit ClaimSubmitted(claimId, msg.sender, amount, reason, block.timestamp);
    }

    /**
     * @notice Resolve a pending insurance claim.
     * @dev Only owner (deployer/governance) can resolve claims.
     * @param claimId The claim to resolve.
     * @param approved Whether to approve the claim.
     * @param payoutAmount Actual payout amount (can be less than requested).
     */
    function resolveClaim(
        uint256 claimId,
        bool approved,
        uint256 payoutAmount
    )
        external
        nonReentrant
        onlyOwner
    {
        require(claimId > 0 && claimId <= _claimCounter, "VaultfireBondInsurancePool: claim does not exist");

        InsuranceClaim storage claim = _claims[claimId];
        require(claim.status == ClaimStatus.Pending, "VaultfireBondInsurancePool: claim already resolved");

        if (approved) {
            require(payoutAmount > 0, "VaultfireBondInsurancePool: zero payout on approval");
            require(payoutAmount <= claim.amount, "VaultfireBondInsurancePool: payout exceeds requested");
            require(payoutAmount <= totalPoolBalance, "VaultfireBondInsurancePool: insufficient pool balance");

            claim.status = ClaimStatus.Approved;
            claim.payout = payoutAmount;
            claim.resolvedAt = block.timestamp;

            totalPoolBalance -= payoutAmount;
            totalPaidOut += payoutAmount;

            (bool ok, ) = claim.claimant.call{value: payoutAmount}("");
            require(ok, "VaultfireBondInsurancePool: payout transfer failed");
        } else {
            claim.status = ClaimStatus.Denied;
            claim.payout = 0;
            claim.resolvedAt = block.timestamp;
        }

        emit ClaimResolved(claimId, claim.claimant, claim.status, claim.payout, block.timestamp);
    }

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

    function getClaim(uint256 claimId) external view returns (InsuranceClaim memory) {
        require(claimId > 0 && claimId <= _claimCounter, "VaultfireBondInsurancePool: claim does not exist");
        return _claims[claimId];
    }

    function getContributorProfile(address contributor)
        external
        view
        returns (
            uint256 totalContributed,
            uint256 contributionCount,
            bool insured,
            uint256 firstContributionAt
        )
    {
        ContributorProfile memory p = contributors[contributor];
        return (p.totalContributed, p.contributionCount, p.insured, p.firstContributionAt);
    }

    function isInsured(address agent) external view returns (bool) {
        return contributors[agent].insured;
    }

    function getPoolStats()
        external
        view
        returns (
            uint256 balance,
            uint256 contributorCount,
            uint256 claims,
            uint256 paidOut
        )
    {
        return (totalPoolBalance, totalContributors, totalClaimsCount, totalPaidOut);
    }

    function claimCount() external view returns (uint256) {
        return _claimCounter;
    }
}
