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

import "./IVaultfireIdentity.sol";

/// @title SessionKeyManager — Time-limited, scoped signing keys for AI agents
/// @author Vaultfire Protocol
/// @notice Creates ephemeral session keys with specific permissions that expire
///         automatically. Agents get the minimum authority needed for a task,
///         nothing more. If the session expires or the agent goes rogue, the
///         key is worthless.
/// @dev Session keys are mapped to their creator's ERC-8004 identity.
contract SessionKeyManager {
    // ═══════════════════════════════════════════════════
    // STRUCTS
    // ═══════════════════════════════════════════════════

    struct SessionKey {
        address owner;             // ERC-8004 registered agent who created this session
        address sessionAddress;    // The address authorized to sign
        uint256 maxValue;          // Max native token per transaction (wei)
        uint256 totalLimit;        // Max total spend over session lifetime (wei)
        uint256 totalSpent;        // Running total spent by this session key
        uint256 expiresAt;         // Unix timestamp when session expires
        uint256 createdAt;         // Unix timestamp of creation
        bool revoked;              // Manually revoked by owner
        bool allowAnyContract;     // If true, can call any contract
    }

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

    IVaultfireIdentity public immutable identityRegistry;

    /// @notice Incrementing session ID counter
    uint256 public sessionCount;

    /// @notice Session ID → SessionKey data
    mapping(uint256 => SessionKey) public sessions;

    /// @notice Session ID → allowed contract address → bool
    mapping(uint256 => mapping(address => bool)) public sessionAllowedContracts;

    /// @notice Owner address → list of session IDs they created
    mapping(address => uint256[]) public ownerSessions;

    /// @notice Session address → session ID (for reverse lookup)
    mapping(address => uint256) public sessionToId;

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

    event SessionCreated(
        uint256 indexed sessionId,
        address indexed owner,
        address indexed sessionAddress,
        uint256 expiresAt,
        uint256 maxValue,
        uint256 totalLimit
    );

    event SessionUsed(
        uint256 indexed sessionId,
        address indexed target,
        uint256 value
    );

    event SessionRevoked(uint256 indexed sessionId, address indexed owner);
    event AllSessionsRevoked(address indexed owner);

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

    error NotRegistered();
    error NotSessionOwner();
    error SessionExpired();
    error SessionRevoked_();
    error ExceedsSessionValueLimit(uint256 requested, uint256 limit);
    error ExceedsSessionTotalLimit(uint256 wouldSpend, uint256 limit);
    error ContractNotAllowedInSession(address target);
    error InvalidSessionAddress();
    error InvalidDuration();
    error ZeroAddress();

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

    constructor(address _identityRegistry) {
        if (_identityRegistry == address(0)) revert ZeroAddress();
        identityRegistry = IVaultfireIdentity(_identityRegistry);
    }

    // ═══════════════════════════════════════════════════
    // CREATE SESSION KEY
    // ═══════════════════════════════════════════════════

    /// @notice Create a new session key with scoped permissions
    /// @param sessionAddress The address to authorize as a session signer
    /// @param maxValue Max native token per transaction (wei)
    /// @param totalLimit Max total spend for the session lifetime (wei)
    /// @param durationSeconds How long the session key is valid (seconds)
    /// @param allowedContracts Contracts this session can interact with (empty = any)
    /// @return sessionId The ID of the created session
    function createSession(
        address sessionAddress,
        uint256 maxValue,
        uint256 totalLimit,
        uint256 durationSeconds,
        address[] calldata allowedContracts
    ) external returns (uint256 sessionId) {
        // Verify caller is registered via ERC-8004 getAgent()
        (string memory agentURI,,,) = identityRegistry.getAgent(msg.sender);
        if (bytes(agentURI).length == 0) revert NotRegistered();
        if (sessionAddress == address(0)) revert InvalidSessionAddress();
        if (durationSeconds == 0) revert InvalidDuration();

        sessionId = sessionCount++;
        uint256 expiresAt = block.timestamp + durationSeconds;

        sessions[sessionId] = SessionKey({
            owner: msg.sender,
            sessionAddress: sessionAddress,
            maxValue: maxValue,
            totalLimit: totalLimit,
            totalSpent: 0,
            expiresAt: expiresAt,
            createdAt: block.timestamp,
            revoked: false,
            allowAnyContract: allowedContracts.length == 0
        });

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

        ownerSessions[msg.sender].push(sessionId);
        sessionToId[sessionAddress] = sessionId;

        emit SessionCreated(
            sessionId,
            msg.sender,
            sessionAddress,
            expiresAt,
            maxValue,
            totalLimit
        );
    }

    // ═══════════════════════════════════════════════════
    // VALIDATE SESSION — Called by KeyVault before executing
    // ═══════════════════════════════════════════════════

    /// @notice Validate that a session key can execute a specific transaction
    /// @param sessionId The session to validate
    /// @param target The contract being called
    /// @param value The value being sent
    /// @return valid True if the session key is authorized for this transaction
    function validateSession(
        uint256 sessionId,
        address target,
        uint256 value
    ) external returns (bool valid) {
        SessionKey storage session = sessions[sessionId];

        // Check expiry
        if (block.timestamp > session.expiresAt) revert SessionExpired();

        // Check revocation
        if (session.revoked) revert SessionRevoked_();

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

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

        // Check contract allowlist
        if (!session.allowAnyContract && !sessionAllowedContracts[sessionId][target]) {
            revert ContractNotAllowedInSession(target);
        }

        // Update spending
        session.totalSpent += value;

        emit SessionUsed(sessionId, target, value);
        return true;
    }

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

    /// @notice Revoke a specific session key
    function revokeSession(uint256 sessionId) external {
        if (sessions[sessionId].owner != msg.sender) revert NotSessionOwner();
        sessions[sessionId].revoked = true;
        emit SessionRevoked(sessionId, msg.sender);
    }

    /// @notice Revoke ALL session keys for the caller (emergency)
    function revokeAllSessions() external {
        uint256[] storage ids = ownerSessions[msg.sender];
        for (uint256 i = 0; i < ids.length; i++) {
            if (!sessions[ids[i]].revoked) {
                sessions[ids[i]].revoked = true;
                emit SessionRevoked(ids[i], msg.sender);
            }
        }
        emit AllSessionsRevoked(msg.sender);
    }

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

    /// @notice Check if a session key is currently valid (not expired, not revoked)
    function isSessionValid(uint256 sessionId) external view returns (bool) {
        SessionKey storage session = sessions[sessionId];
        return !session.revoked
            && block.timestamp <= session.expiresAt
            && session.totalSpent < session.totalLimit;
    }

    /// @notice Get the remaining spend allowance for a session
    function sessionRemaining(uint256 sessionId) external view returns (uint256) {
        SessionKey storage session = sessions[sessionId];
        if (session.revoked || block.timestamp > session.expiresAt) return 0;
        if (session.totalSpent >= session.totalLimit) return 0;
        return session.totalLimit - session.totalSpent;
    }

    /// @notice Get all session IDs for an owner
    function getOwnerSessions(address owner_) external view returns (uint256[] memory) {
        return ownerSessions[owner_];
    }

    /// @notice Get full session data
    function getSession(uint256 sessionId) external view returns (SessionKey memory) {
        return sessions[sessionId];
    }

    /// @notice Check if a contract is allowed for a session
    function isContractAllowed(uint256 sessionId, address target) external view returns (bool) {
        if (sessions[sessionId].allowAnyContract) return true;
        return sessionAllowedContracts[sessionId][target];
    }
}
