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

import "./IVaultfireIdentity.sol";
import "./IVaultfireBonds.sol";
import "./IVaultfireReputation.sol";

/// @title DelegationRegistry — Encrypted key delegation between verified AI agents
/// @author Vaultfire Protocol
/// @notice Like XMTP for keys. Agents can delegate scoped signing authority to
///         other verified agents, gated by identity, bonds, and reputation.
///         Delegations are time-limited and auto-revoke if trust conditions change.
/// @dev This is the core "XMTP for keys" contract. All delegations are tied to
///      ERC-8004 identities and enforced on-chain.
contract DelegationRegistry {
    // ═══════════════════════════════════════════════════
    // STRUCTS
    // ═══════════════════════════════════════════════════

    struct Delegation {
        address delegator;          // Agent granting authority (ERC-8004 registered)
        address delegate;           // Agent receiving authority (ERC-8004 registered)
        uint256 maxValue;           // Max native token per transaction (wei)
        uint256 totalLimit;         // Max total spend over delegation lifetime (wei)
        uint256 totalSpent;         // Running total spent by delegate
        uint256 expiresAt;          // Unix timestamp when delegation expires
        uint256 createdAt;          // Unix timestamp of creation
        uint256 minBondRequired;    // Min accountability bond the delegate must have (wei)
        uint256 minReputation;      // Min reputation score the delegate must have (0-100)
        bool revoked;               // Manually revoked by delegator
        bool accepted;              // Delegate has accepted the delegation
        bool hasContractAllowlist;  // If true, only listed contracts are allowed
        bool hasFunctionAllowlist;  // If true, only listed function selectors are allowed
        bytes32 encryptedKeyHash;   // Hash of the encrypted session key (off-chain ref)
    }

    // ═══════════════════════════════════════════════════
    // STATE
    // ═══════════════════════════════════════════════════

    IVaultfireIdentity public immutable identityRegistry;
    IVaultfireBonds public immutable bondsRegistry;
    IVaultfireReputation public immutable reputationRegistry;

    uint256 public delegationCount;

    /// @notice Delegation ID → Delegation data
    mapping(uint256 => Delegation) public delegations;

    /// @notice Delegation ID → allowed contracts
    mapping(uint256 => mapping(address => bool)) public delegationAllowedContracts;

    /// @notice Delegation ID → allowed function selectors
    mapping(uint256 => mapping(bytes4 => bool)) public delegationAllowedFunctions;

    /// @notice Delegator → list of delegation IDs they created
    mapping(address => uint256[]) public delegatorDelegations;

    /// @notice Delegate → list of delegation IDs they received
    mapping(address => uint256[]) public delegateDelegations;

    /// @notice Delegation ID → delegate's bond ID (for re-verification)
    mapping(uint256 => uint256) public delegationBondIds;

    // ═══════════════════════════════════════════════════
    // EVENTS
    // ═══════════════════════════════════════════════════

    event DelegationCreated(
        uint256 indexed delegationId,
        address indexed delegator,
        address indexed delegate,
        uint256 expiresAt,
        uint256 maxValue,
        uint256 totalLimit,
        uint256 minBondRequired,
        uint256 minReputation
    );

    event DelegationAccepted(uint256 indexed delegationId, address indexed delegate);
    event DelegationUsed(uint256 indexed delegationId, address indexed target, uint256 value);
    event DelegationRevoked(uint256 indexed delegationId, address indexed delegator);
    event AllDelegationsRevoked(address indexed delegator);
    event TrustViolation(uint256 indexed delegationId, string reason);

    // ═══════════════════════════════════════════════════
    // ERRORS
    // ═══════════════════════════════════════════════════

    error NotRegistered();
    error NotDelegator();
    error NotDelegate();
    error DelegationExpired();
    error DelegationRevoked_();
    error DelegationNotAccepted();
    error AlreadyAccepted();
    error InsufficientBond(uint256 actual, uint256 required);
    error InsufficientReputation(uint256 actual, uint256 required);
    error ExceedsDelegationValueLimit(uint256 requested, uint256 limit);
    error ExceedsDelegationTotalLimit(uint256 wouldSpend, uint256 limit);
    error ContractNotAllowedInDelegation(address target);
    error FunctionNotAllowedInDelegation(bytes4 selector);
    error CannotDelegateToSelf();
    error InvalidDuration();
    error ZeroAddress();

    // ═══════════════════════════════════════════════════
    // CONSTRUCTOR
    // ═══════════════════════════════════════════════════

    constructor(
        address _identityRegistry,
        address _bondsRegistry,
        address _reputationRegistry
    ) {
        if (_identityRegistry == address(0)) revert ZeroAddress();
        if (_bondsRegistry == address(0)) revert ZeroAddress();
        if (_reputationRegistry == address(0)) revert ZeroAddress();

        identityRegistry = IVaultfireIdentity(_identityRegistry);
        bondsRegistry = IVaultfireBonds(_bondsRegistry);
        reputationRegistry = IVaultfireReputation(_reputationRegistry);
    }

    // ═══════════════════════════════════════════════════
    // CREATE DELEGATION
    // ═══════════════════════════════════════════════════

    /// @notice Create a new delegation to another verified agent
    /// @param delegate The agent to delegate authority to
    /// @param maxValue Max native token per transaction (wei)
    /// @param totalLimit Max total spend over delegation lifetime (wei)
    /// @param durationSeconds How long the delegation is valid
    /// @param minBondRequired Minimum bond the delegate must maintain (wei)
    /// @param minReputation Minimum reputation score the delegate must have
    /// @param allowedContracts Contracts the delegate can interact with (empty = any)
    /// @param allowedFunctions Function selectors the delegate can call (empty = any)
    /// @param encryptedKeyHash Hash of the off-chain encrypted session key
    /// @return delegationId The ID of the created delegation
    function createDelegation(
        address delegate,
        uint256 maxValue,
        uint256 totalLimit,
        uint256 durationSeconds,
        uint256 minBondRequired,
        uint256 minReputation,
        address[] calldata allowedContracts,
        bytes4[] calldata allowedFunctions,
        bytes32 encryptedKeyHash
    ) external returns (uint256 delegationId) {
        // Both parties must be registered ERC-8004 agents
        (string memory senderURI,,,) = identityRegistry.getAgent(msg.sender);
        if (bytes(senderURI).length == 0) revert NotRegistered();
        (string memory delegateURI,,,) = identityRegistry.getAgent(delegate);
        if (bytes(delegateURI).length == 0) revert NotRegistered();
        if (delegate == msg.sender) revert CannotDelegateToSelf();
        if (durationSeconds == 0) revert InvalidDuration();

        delegationId = delegationCount++;
        uint256 expiresAt = block.timestamp + durationSeconds;

        delegations[delegationId] = Delegation({
            delegator: msg.sender,
            delegate: delegate,
            maxValue: maxValue,
            totalLimit: totalLimit,
            totalSpent: 0,
            expiresAt: expiresAt,
            createdAt: block.timestamp,
            minBondRequired: minBondRequired,
            minReputation: minReputation,
            revoked: false,
            accepted: false,
            hasContractAllowlist: allowedContracts.length > 0,
            hasFunctionAllowlist: allowedFunctions.length > 0,
            encryptedKeyHash: encryptedKeyHash
        });

        // Set allowed contracts
        for (uint256 i = 0; i < allowedContracts.length; i++) {
            delegationAllowedContracts[delegationId][allowedContracts[i]] = true;
        }

        // Set allowed function selectors
        for (uint256 i = 0; i < allowedFunctions.length; i++) {
            delegationAllowedFunctions[delegationId][allowedFunctions[i]] = true;
        }

        delegatorDelegations[msg.sender].push(delegationId);
        delegateDelegations[delegate].push(delegationId);

        emit DelegationCreated(
            delegationId,
            msg.sender,
            delegate,
            expiresAt,
            maxValue,
            totalLimit,
            minBondRequired,
            minReputation
        );
    }

    // ═══════════════════════════════════════════════════
    // ACCEPT DELEGATION — Delegate verifies their own eligibility
    // ═══════════════════════════════════════════════════

    /// @notice Accept a delegation (delegate must meet trust requirements)
    /// @param delegationId The delegation to accept
    /// @param bondId The delegate's accountability bond ID (verified on-chain)
    function acceptDelegation(uint256 delegationId, uint256 bondId) external {
        Delegation storage d = delegations[delegationId];

        if (d.delegate != msg.sender) revert NotDelegate();
        if (d.accepted) revert AlreadyAccepted();
        if (d.revoked) revert DelegationRevoked_();
        if (block.timestamp > d.expiresAt) revert DelegationExpired();

        // Verify delegate meets bond requirement using their bondId
        if (d.minBondRequired > 0) {
            (address creator, uint256 stakeAmount, , bool active) = bondsRegistry.getBond(bondId);
            // Verify the bond belongs to the delegate and meets requirements
            if (creator != msg.sender) revert InsufficientBond(0, d.minBondRequired);
            if (!active || stakeAmount < d.minBondRequired) {
                revert InsufficientBond(stakeAmount, d.minBondRequired);
            }
            // Store the bondId for future re-verification
            delegationBondIds[delegationId] = bondId;
        }

        // Verify delegate meets reputation requirement
        if (d.minReputation > 0) {
            (uint256 score, , ) = reputationRegistry.getReputation(msg.sender);
            if (score < d.minReputation) {
                revert InsufficientReputation(score, d.minReputation);
            }
        }

        d.accepted = true;
        emit DelegationAccepted(delegationId, msg.sender);
    }

    // ═══════════════════════════════════════════════════
    // VERIFY TRUST — Anyone can trigger re-verification
    // ═══════════════════════════════════════════════════

    /// @notice Re-verify that a delegate still meets bond and reputation requirements.
    ///         Anyone can call this (delegator, watchdog, bot). If trust conditions
    ///         are no longer met, the delegation is permanently auto-revoked.
    ///         This does NOT revert on failure — it commits the revocation.
    /// @param delegationId The delegation to re-verify
    /// @return trusted True if the delegate still meets all trust requirements
    function verifyTrust(uint256 delegationId) external returns (bool trusted) {
        Delegation storage d = delegations[delegationId];
        if (d.revoked) return false;
        if (!d.accepted) return false;
        if (block.timestamp > d.expiresAt) return false;

        _verifyTrustRequirements(delegationId);
        return !d.revoked;
    }

    // ═══════════════════════════════════════════════════
    // VALIDATE DELEGATION — Check if a delegate can execute
    // ═══════════════════════════════════════════════════

    /// @notice Validate and record delegation usage for a specific transaction
    /// @param delegationId The delegation to validate
    /// @param target The contract being called
    /// @param value The value being sent
    /// @param selector The function selector being called
    /// @return valid True if the delegation authorizes this transaction
    function validateDelegation(
        uint256 delegationId,
        address target,
        uint256 value,
        bytes4 selector
    ) external returns (bool valid) {
        Delegation storage d = delegations[delegationId];

        // Basic checks
        if (d.revoked) revert DelegationRevoked_();
        if (!d.accepted) revert DelegationNotAccepted();
        if (block.timestamp > d.expiresAt) revert DelegationExpired();

        // Check per-tx value limit
        if (value > d.maxValue) {
            revert ExceedsDelegationValueLimit(value, d.maxValue);
        }

        // Check total lifetime limit
        if (d.totalSpent + value > d.totalLimit) {
            revert ExceedsDelegationTotalLimit(d.totalSpent + value, d.totalLimit);
        }

        // Enforce contract allowlist — if set, target MUST be in the list
        if (d.hasContractAllowlist) {
            if (!delegationAllowedContracts[delegationId][target]) {
                revert ContractNotAllowedInDelegation(target);
            }
        }

        // Enforce function selector allowlist — if set, selector MUST be in the list
        if (d.hasFunctionAllowlist) {
            if (!delegationAllowedFunctions[delegationId][selector]) {
                revert FunctionNotAllowedInDelegation(selector);
            }
        }

        // Update spending (effects before interactions)
        d.totalSpent += value;

        emit DelegationUsed(delegationId, target, value);
        return true;
    }

    // ═══════════════════════════════════════════════════
    // REVOKE
    // ═══════════════════════════════════════════════════

    /// @notice Revoke a specific delegation (only delegator)
    function revokeDelegation(uint256 delegationId) external {
        if (delegations[delegationId].delegator != msg.sender) revert NotDelegator();
        delegations[delegationId].revoked = true;
        emit DelegationRevoked(delegationId, msg.sender);
    }

    /// @notice Revoke ALL delegations created by the caller (emergency)
    function revokeAllDelegations() external {
        uint256[] storage ids = delegatorDelegations[msg.sender];
        for (uint256 i = 0; i < ids.length; i++) {
            if (!delegations[ids[i]].revoked) {
                delegations[ids[i]].revoked = true;
                emit DelegationRevoked(ids[i], msg.sender);
            }
        }
        emit AllDelegationsRevoked(msg.sender);
    }

    // ═══════════════════════════════════════════════════
    // VIEW FUNCTIONS
    // ═══════════════════════════════════════════════════

    /// @notice Check if a delegation is currently valid
    function isDelegationValid(uint256 delegationId) external view returns (bool) {
        Delegation storage d = delegations[delegationId];
        return !d.revoked
            && d.accepted
            && block.timestamp <= d.expiresAt
            && d.totalSpent < d.totalLimit;
    }

    /// @notice Get remaining spend allowance for a delegation
    function delegationRemaining(uint256 delegationId) external view returns (uint256) {
        Delegation storage d = delegations[delegationId];
        if (d.revoked || !d.accepted || block.timestamp > d.expiresAt) return 0;
        if (d.totalSpent >= d.totalLimit) return 0;
        return d.totalLimit - d.totalSpent;
    }

    /// @notice Get all delegation IDs created by a delegator
    function getDelegatorDelegations(address delegator) external view returns (uint256[] memory) {
        return delegatorDelegations[delegator];
    }

    /// @notice Get all delegation IDs received by a delegate
    function getDelegateDelegations(address delegate) external view returns (uint256[] memory) {
        return delegateDelegations[delegate];
    }

    /// @notice Get full delegation data
    function getDelegation(uint256 delegationId) external view returns (Delegation memory) {
        return delegations[delegationId];
    }

    // ═══════════════════════════════════════════════════
    // INTERNAL — Trust verification
    // ═══════════════════════════════════════════════════

    /// @dev Re-verify that the delegate still meets bond and reputation requirements.
    ///      If they don't, the delegation is auto-revoked with a trust violation event.
    ///      IMPORTANT: This function does NOT revert — it sets revoked = true and returns.
    ///      The caller must check d.revoked after calling this. If we reverted here,
    ///      the EVM would roll back the revocation and the delegation would remain active.
    function _verifyTrustRequirements(uint256 delegationId) internal {
        Delegation storage d = delegations[delegationId];

        // Re-check bond using the stored bondId from acceptance
        if (d.minBondRequired > 0) {
            uint256 bondId = delegationBondIds[delegationId];
            try bondsRegistry.getBond(bondId) returns (
                address creator, uint256 stakeAmount, uint256, bool active
            ) {
                // Verify bond still belongs to delegate and meets requirements
                if (creator != d.delegate || !active || stakeAmount < d.minBondRequired) {
                    d.revoked = true;
                    emit TrustViolation(delegationId, "Bond requirement no longer met");
                    return; // Do NOT revert — state change must persist
                }
            } catch {
                d.revoked = true;
                emit TrustViolation(delegationId, "Bond check failed");
                return;
            }
        }

        // Re-check reputation
        if (d.minReputation > 0) {
            try reputationRegistry.getReputation(d.delegate) returns (
                uint256 score, uint256, uint256
            ) {
                if (score < d.minReputation) {
                    d.revoked = true;
                    emit TrustViolation(delegationId, "Reputation dropped below threshold");
                    return;
                }
            } catch {
                d.revoked = true;
                emit TrustViolation(delegationId, "Reputation check failed");
                return;
            }
        }
    }
}
