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

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

/// @title ScopedDelegation — ERC-7710-compatible scoped permissions for AI agent delegation
/// @author Vaultfire Protocol
/// @notice Extends Vaultfire's delegation with industry-standard scoped permissions:
///         - Agent-to-agent delegation with progressive permission narrowing
///         - Rate limiting, spending caps, contract allowlists
///         - Compatible with MetaMask Delegation Toolkit and Ledger Agent Policies
/// @dev Implements delegation chains where each hop can only narrow permissions, never widen.
contract ScopedDelegation {
    // ═══════════════════════════════════════════════════
    // STRUCTS
    // ═══════════════════════════════════════════════════

    struct Scope {
        uint256 maxValuePerTx;          // Max native token per transaction (wei)
        uint256 maxTotalValue;          // Max total value over scope lifetime (wei)
        uint256 totalSpent;             // Running total spent
        uint256 maxTxCount;             // Max number of transactions allowed (0 = unlimited)
        uint256 txCount;                // Running transaction count
        uint256 rateLimit;              // Min seconds between transactions (0 = no limit)
        uint256 lastTxAt;               // Timestamp of last transaction
        uint256 expiresAt;              // When this scope expires
        uint256 createdAt;              // When this scope was created
        address grantor;                // Who granted this scope
        address grantee;                // Who received this scope
        bool revoked;                   // Whether scope has been revoked
        bool active;                    // Whether scope is currently active
    }

    struct DelegationChain {
        uint256[] scopeIds;             // Chain of scope IDs from root to leaf
        uint256 depth;                  // How deep in the chain (0 = root)
        address rootGrantor;            // Original authority holder
        bool valid;                     // Whether chain is still valid
    }

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

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

    address public owner;

    uint256 public totalScopes;
    uint256 public totalChains;
    uint256 public constant MAX_CHAIN_DEPTH = 5;  // Max delegation chain depth

    mapping(uint256 => Scope) public scopes;
    mapping(uint256 => DelegationChain) public chains;

    // Agent's granted scopes (as grantor)
    mapping(address => uint256[]) public grantedScopes;
    // Agent's received scopes (as grantee)
    mapping(address => uint256[]) public receivedScopes;

    // Contract allowlists per scope: scopeId => contract => allowed
    mapping(uint256 => mapping(address => bool)) public contractAllowlist;
    // Function allowlists per scope: scopeId => bytes4 selector => allowed
    mapping(uint256 => mapping(bytes4 => bool)) public functionAllowlist;

    // Scope has contract restrictions
    mapping(uint256 => bool) public hasContractRestrictions;
    // Scope has function restrictions
    mapping(uint256 => bool) public hasFunctionRestrictions;

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

    event ScopeCreated(uint256 indexed scopeId, address indexed grantor, address indexed grantee, uint256 expiresAt);
    event ScopeRevoked(uint256 indexed scopeId, address indexed revokedBy);
    event ScopeUsed(uint256 indexed scopeId, address indexed grantee, uint256 value);
    event ChainCreated(uint256 indexed chainId, address indexed rootGrantor, uint256 depth);
    event ContractAllowlistUpdated(uint256 indexed scopeId, address indexed contractAddr, bool allowed);
    event FunctionAllowlistUpdated(uint256 indexed scopeId, bytes4 indexed selector, bool allowed);

    // ═══════════════════════════════════════════════════
    // MODIFIERS
    // ═══════════════════════════════════════════════════

    modifier onlyOwner() {
        require(msg.sender == owner, "ScopedDelegation: caller is not owner");
        _;
    }

    modifier onlyRegisteredAgent(address agent) {
        (string memory uri, bool active, , ) = identityRegistry.getAgent(agent);
        require(bytes(uri).length > 0 && active, "ScopedDelegation: agent not registered or inactive");
        _;
    }

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

    constructor(
        address _identityRegistry,
        address _bondsRegistry,
        address _reputationRegistry
    ) {
        require(_identityRegistry != address(0), "ScopedDelegation: zero identity address");
        require(_bondsRegistry != address(0), "ScopedDelegation: zero bonds address");
        require(_reputationRegistry != address(0), "ScopedDelegation: zero reputation address");

        identityRegistry = IVaultfireIdentity(_identityRegistry);
        bondsRegistry = IVaultfireBonds(_bondsRegistry);
        reputationRegistry = IVaultfireReputation(_reputationRegistry);
        owner = msg.sender;
    }

    // ═══════════════════════════════════════════════════
    // SCOPE CREATION
    // ═══════════════════════════════════════════════════

    /// @notice Create a new delegation scope from one agent to another
    /// @param grantee Agent receiving the delegation
    /// @param maxValuePerTx Max value per transaction (wei)
    /// @param maxTotalValue Max total value over lifetime (wei)
    /// @param maxTxCount Max transaction count (0 = unlimited)
    /// @param rateLimit Min seconds between transactions (0 = no limit)
    /// @param duration Duration in seconds
    function createScope(
        address grantee,
        uint256 maxValuePerTx,
        uint256 maxTotalValue,
        uint256 maxTxCount,
        uint256 rateLimit,
        uint256 duration
    ) external onlyRegisteredAgent(msg.sender) onlyRegisteredAgent(grantee) returns (uint256 scopeId) {
        require(grantee != msg.sender, "ScopedDelegation: cannot self-delegate");
        require(duration > 0 && duration <= 365 days, "ScopedDelegation: invalid duration");
        require(maxValuePerTx > 0 || maxTxCount > 0, "ScopedDelegation: must have at least one limit");

        scopeId = totalScopes;
        totalScopes++;

        scopes[scopeId] = Scope({
            maxValuePerTx: maxValuePerTx,
            maxTotalValue: maxTotalValue,
            totalSpent: 0,
            maxTxCount: maxTxCount,
            txCount: 0,
            rateLimit: rateLimit,
            lastTxAt: 0,
            expiresAt: block.timestamp + duration,
            createdAt: block.timestamp,
            grantor: msg.sender,
            grantee: grantee,
            revoked: false,
            active: true
        });

        grantedScopes[msg.sender].push(scopeId);
        receivedScopes[grantee].push(scopeId);

        emit ScopeCreated(scopeId, msg.sender, grantee, block.timestamp + duration);
    }

    /// @notice Sub-delegate: create a narrower scope from a received scope (delegation chain)
    /// @dev The new scope must be strictly narrower than the parent scope
    /// @param parentScopeId The scope being sub-delegated
    /// @param grantee New grantee for the sub-delegation
    /// @param maxValuePerTx Must be <= parent's maxValuePerTx
    /// @param maxTotalValue Must be <= parent's remaining value
    /// @param maxTxCount Must be <= parent's remaining tx count
    /// @param rateLimit Must be >= parent's rateLimit (more restrictive)
    /// @param duration Must be <= parent's remaining duration
    function subDelegate(
        uint256 parentScopeId,
        address grantee,
        uint256 maxValuePerTx,
        uint256 maxTotalValue,
        uint256 maxTxCount,
        uint256 rateLimit,
        uint256 duration
    ) external onlyRegisteredAgent(grantee) returns (uint256 scopeId) {
        Scope storage parent = scopes[parentScopeId];
        require(parent.active && !parent.revoked, "ScopedDelegation: parent scope not active");
        require(parent.grantee == msg.sender, "ScopedDelegation: caller is not grantee of parent");
        require(block.timestamp < parent.expiresAt, "ScopedDelegation: parent scope expired");
        require(grantee != msg.sender, "ScopedDelegation: cannot self-delegate");

        // Enforce progressive narrowing — new scope must be strictly more restrictive
        require(maxValuePerTx <= parent.maxValuePerTx, "ScopedDelegation: maxValuePerTx exceeds parent");
        uint256 parentRemaining = parent.maxTotalValue - parent.totalSpent;
        require(maxTotalValue <= parentRemaining, "ScopedDelegation: maxTotalValue exceeds parent remaining");
        if (parent.maxTxCount > 0) {
            uint256 parentRemainingTx = parent.maxTxCount - parent.txCount;
            require(maxTxCount <= parentRemainingTx, "ScopedDelegation: maxTxCount exceeds parent remaining");
        }
        require(rateLimit >= parent.rateLimit, "ScopedDelegation: rateLimit less restrictive than parent");
        uint256 parentRemainingDuration = parent.expiresAt - block.timestamp;
        require(duration <= parentRemainingDuration, "ScopedDelegation: duration exceeds parent remaining");

        scopeId = totalScopes;
        totalScopes++;

        scopes[scopeId] = Scope({
            maxValuePerTx: maxValuePerTx,
            maxTotalValue: maxTotalValue,
            totalSpent: 0,
            maxTxCount: maxTxCount,
            txCount: 0,
            rateLimit: rateLimit,
            lastTxAt: 0,
            expiresAt: block.timestamp + duration,
            createdAt: block.timestamp,
            grantor: msg.sender,
            grantee: grantee,
            revoked: false,
            active: true
        });

        grantedScopes[msg.sender].push(scopeId);
        receivedScopes[grantee].push(scopeId);

        emit ScopeCreated(scopeId, msg.sender, grantee, block.timestamp + duration);
    }

    // ═══════════════════════════════════════════════════
    // SCOPE ALLOWLISTS
    // ═══════════════════════════════════════════════════

    /// @notice Add or remove a contract from the scope's allowlist
    function setContractAllowlist(uint256 scopeId, address contractAddr, bool allowed) external {
        Scope storage scope = scopes[scopeId];
        require(scope.grantor == msg.sender, "ScopedDelegation: not grantor");
        require(scope.active && !scope.revoked, "ScopedDelegation: scope not active");

        contractAllowlist[scopeId][contractAddr] = allowed;
        hasContractRestrictions[scopeId] = true;

        emit ContractAllowlistUpdated(scopeId, contractAddr, allowed);
    }

    /// @notice Add or remove a function selector from the scope's allowlist
    function setFunctionAllowlist(uint256 scopeId, bytes4 selector, bool allowed) external {
        Scope storage scope = scopes[scopeId];
        require(scope.grantor == msg.sender, "ScopedDelegation: not grantor");
        require(scope.active && !scope.revoked, "ScopedDelegation: scope not active");

        functionAllowlist[scopeId][selector] = allowed;
        hasFunctionRestrictions[scopeId] = true;

        emit FunctionAllowlistUpdated(scopeId, selector, allowed);
    }

    // ═══════════════════════════════════════════════════
    // SCOPE USAGE & VALIDATION
    // ═══════════════════════════════════════════════════

    /// @notice Validate and record a scope usage (called before executing delegated action)
    /// @param scopeId The scope being used
    /// @param value The value of the transaction
    /// @param targetContract The contract being called (address(0) if N/A)
    /// @param functionSelector The function being called (bytes4(0) if N/A)
    function useScope(
        uint256 scopeId,
        uint256 value,
        address targetContract,
        bytes4 functionSelector
    ) external returns (bool) {
        Scope storage scope = scopes[scopeId];
        require(scope.grantee == msg.sender, "ScopedDelegation: not grantee");
        require(scope.active && !scope.revoked, "ScopedDelegation: scope not active");
        require(block.timestamp < scope.expiresAt, "ScopedDelegation: scope expired");

        // Check value limits
        require(value <= scope.maxValuePerTx, "ScopedDelegation: exceeds per-tx limit");
        require(scope.totalSpent + value <= scope.maxTotalValue, "ScopedDelegation: exceeds total limit");

        // Check transaction count
        if (scope.maxTxCount > 0) {
            require(scope.txCount < scope.maxTxCount, "ScopedDelegation: tx count exceeded");
        }

        // Check rate limit
        if (scope.rateLimit > 0 && scope.lastTxAt > 0) {
            require(block.timestamp >= scope.lastTxAt + scope.rateLimit, "ScopedDelegation: rate limited");
        }

        // Check contract allowlist
        if (hasContractRestrictions[scopeId] && targetContract != address(0)) {
            require(contractAllowlist[scopeId][targetContract], "ScopedDelegation: contract not allowed");
        }

        // Check function allowlist
        if (hasFunctionRestrictions[scopeId] && functionSelector != bytes4(0)) {
            require(functionAllowlist[scopeId][functionSelector], "ScopedDelegation: function not allowed");
        }

        // Record usage
        scope.totalSpent += value;
        scope.txCount++;
        scope.lastTxAt = block.timestamp;

        emit ScopeUsed(scopeId, msg.sender, value);
        return true;
    }

    // ═══════════════════════════════════════════════════
    // SCOPE MANAGEMENT
    // ═══════════════════════════════════════════════════

    /// @notice Revoke a scope (grantor only)
    function revokeScope(uint256 scopeId) external {
        Scope storage scope = scopes[scopeId];
        require(scope.grantor == msg.sender || msg.sender == owner, "ScopedDelegation: not authorized");
        require(scope.active && !scope.revoked, "ScopedDelegation: scope not active");

        scope.revoked = true;
        scope.active = false;

        emit ScopeRevoked(scopeId, msg.sender);
    }

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

    /// @notice Check if a scope is currently valid
    function isScopeValid(uint256 scopeId) external view returns (bool) {
        Scope storage scope = scopes[scopeId];
        return scope.active && !scope.revoked && block.timestamp < scope.expiresAt;
    }

    /// @notice Get remaining allowance for a scope
    function getScopeRemaining(uint256 scopeId) external view returns (
        uint256 remainingValue,
        uint256 remainingTxCount,
        uint256 remainingDuration
    ) {
        Scope storage scope = scopes[scopeId];
        if (!scope.active || scope.revoked || block.timestamp >= scope.expiresAt) {
            return (0, 0, 0);
        }

        remainingValue = scope.maxTotalValue - scope.totalSpent;
        remainingTxCount = scope.maxTxCount > 0 ? scope.maxTxCount - scope.txCount : type(uint256).max;
        remainingDuration = scope.expiresAt - block.timestamp;
    }

    /// @notice Get all scopes granted by an agent
    function getGrantedScopes(address agent) external view returns (uint256[] memory) {
        return grantedScopes[agent];
    }

    /// @notice Get all scopes received by an agent
    function getReceivedScopes(address agent) external view returns (uint256[] memory) {
        return receivedScopes[agent];
    }

    /// @notice Transfer ownership
    function transferOwnership(address newOwner) external onlyOwner {
        require(newOwner != address(0), "ScopedDelegation: zero owner");
        owner = newOwner;
    }
}
