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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./OraclePriceFeed.sol";

/**
 * @title PriceFeedKeeper
 * @notice Keeper contract for automated price feed updates
 * @dev Can be called by external keepers (Chainlink Keepers, Gelato, etc.) to update price feeds
 */
contract PriceFeedKeeper is AccessControl, ReentrancyGuard {
    bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");
    bytes32 public constant UPKEEPER_ROLE = keccak256("UPKEEPER_ROLE"); // Can update keeper addresses

    OraclePriceFeed public oraclePriceFeed;

    // Asset tracking
    address[] public trackedAssets;
    mapping(address => bool) public isTracked;

    // Update configuration (default 6s aligns with PMM mesh automation; admin may set via setUpdateInterval)
    uint256 public updateInterval = 6; // seconds
    mapping(address => uint256) public lastUpdateTime;

    // Keeper configuration
    uint256 public maxUpdatesPerCall = 10; // Maximum assets to update per call
    uint256 public gasBuffer = 50000; // Gas buffer for keeper operations

    event AssetTracked(address indexed asset);
    event AssetUntracked(address indexed asset);
    event PriceFeedsUpdated(address[] assets, uint256 timestamp);
    event UpdateIntervalChanged(uint256 oldInterval, uint256 newInterval);
    event MaxUpdatesPerCallChanged(uint256 oldMax, uint256 newMax);

    constructor(address admin, address oraclePriceFeed_) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(KEEPER_ROLE, admin);
        _grantRole(UPKEEPER_ROLE, admin);

        oraclePriceFeed = OraclePriceFeed(oraclePriceFeed_);
    }

    /**
     * @notice Add asset to tracking list
     * @param asset Address of the asset to track
     */
    function trackAsset(address asset) external onlyRole(UPKEEPER_ROLE) {
        require(asset != address(0), "PriceFeedKeeper: zero address");
        require(!isTracked[asset], "PriceFeedKeeper: already tracked");

        trackedAssets.push(asset);
        isTracked[asset] = true;

        emit AssetTracked(asset);
    }

    /**
     * @notice Remove asset from tracking list
     * @param asset Address of the asset to untrack
     */
    function untrackAsset(address asset) external onlyRole(UPKEEPER_ROLE) {
        require(isTracked[asset], "PriceFeedKeeper: not tracked");

        // Remove from array
        for (uint256 i = 0; i < trackedAssets.length; i++) {
            if (trackedAssets[i] == asset) {
                trackedAssets[i] = trackedAssets[trackedAssets.length - 1];
                trackedAssets.pop();
                break;
            }
        }

        isTracked[asset] = false;
        delete lastUpdateTime[asset];

        emit AssetUntracked(asset);
    }

    /**
     * @notice Check if any assets need updating
     * @return updateNeeded True if any assets need updating
     * @return assets Array of assets that need updating
     */
    function checkUpkeep() public view returns (bool updateNeeded, address[] memory assets) {
        address[] memory needsUpdateList = new address[](trackedAssets.length);
        uint256 count = 0;

        for (uint256 i = 0; i < trackedAssets.length; i++) {
            address asset = trackedAssets[i];
            if (_needsUpdate(asset)) {
                needsUpdateList[count] = asset;
                count++;
            }
        }

        if (count > 0) {
            // Resize array
            assets = new address[](count);
            for (uint256 i = 0; i < count; i++) {
                assets[i] = needsUpdateList[i];
            }
            updateNeeded = true;
        } else {
            assets = new address[](0);
            updateNeeded = false;
        }
    }

    /**
     * @notice Perform upkeep - update price feeds that need updating
     * @return success True if updates were successful
     * @return updatedAssets Array of assets that were updated
     */
    function performUpkeep() external onlyRole(KEEPER_ROLE) nonReentrant returns (
        bool success,
        address[] memory updatedAssets
    ) {
        address[] memory needsUpdateList = new address[](trackedAssets.length);
        uint256 count = 0;

        // Collect assets that need updating
        for (uint256 i = 0; i < trackedAssets.length; i++) {
            address asset = trackedAssets[i];
            if (_needsUpdate(asset) && count < maxUpdatesPerCall) {
                needsUpdateList[count] = asset;
                count++;
            }
        }

        if (count == 0) {
            return (true, new address[](0));
        }

        // Resize array
        updatedAssets = new address[](count);
        for (uint256 i = 0; i < count; i++) {
            updatedAssets[i] = needsUpdateList[i];
        }

        // Update price feeds
        try oraclePriceFeed.updateMultiplePriceFeeds(updatedAssets) {
            // Update last update time
            uint256 currentTime = block.timestamp;
            for (uint256 i = 0; i < count; i++) {
                lastUpdateTime[updatedAssets[i]] = currentTime;
            }

            emit PriceFeedsUpdated(updatedAssets, currentTime);
            success = true;
        } catch {
            success = false;
        }
    }

    /**
     * @notice Update specific assets
     * @param assets Array of asset addresses to update
     */
    function updateAssets(address[] calldata assets) external onlyRole(KEEPER_ROLE) nonReentrant {
        require(assets.length > 0, "PriceFeedKeeper: empty array");
        require(assets.length <= maxUpdatesPerCall, "PriceFeedKeeper: too many assets");

        // Verify all assets are tracked
        for (uint256 i = 0; i < assets.length; i++) {
            require(isTracked[assets[i]], "PriceFeedKeeper: asset not tracked");
        }

        oraclePriceFeed.updateMultiplePriceFeeds(assets);

        uint256 currentTime = block.timestamp;
        for (uint256 i = 0; i < assets.length; i++) {
            lastUpdateTime[assets[i]] = currentTime;
        }

        emit PriceFeedsUpdated(assets, currentTime);
    }

    /**
     * @notice Check if a specific asset needs updating
     * @param asset Address of the asset
     * @return needsUpdate True if asset needs updating
     */
    function needsUpdate(address asset) external view returns (bool) {
        return _needsUpdate(asset);
    }

    /**
     * @notice Get all tracked assets
     * @return assets Array of tracked asset addresses
     */
    function getTrackedAssets() external view returns (address[] memory) {
        return trackedAssets;
    }

    /**
     * @notice Set update interval
     * @param interval New update interval in seconds
     */
    function setUpdateInterval(uint256 interval) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(interval > 0, "PriceFeedKeeper: zero interval");
        uint256 oldInterval = updateInterval;
        updateInterval = interval;
        emit UpdateIntervalChanged(oldInterval, interval);
    }

    /**
     * @notice Set maximum updates per call
     * @param max New maximum updates per call
     */
    function setMaxUpdatesPerCall(uint256 max) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(max > 0, "PriceFeedKeeper: zero max");
        uint256 oldMax = maxUpdatesPerCall;
        maxUpdatesPerCall = max;
        emit MaxUpdatesPerCallChanged(oldMax, max);
    }

    /**
     * @notice Set oracle price feed address
     * @param oraclePriceFeed_ New oracle price feed address
     */
    function setOraclePriceFeed(address oraclePriceFeed_) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(oraclePriceFeed_ != address(0), "PriceFeedKeeper: zero address");
        oraclePriceFeed = OraclePriceFeed(oraclePriceFeed_);
    }

    /**
     * @notice Internal function to check if asset needs update
     * @param asset Address of the asset
     * @return True if asset needs updating
     */
    function _needsUpdate(address asset) internal view returns (bool) {
        if (!isTracked[asset]) {
            return false;
        }

        uint256 lastUpdate = lastUpdateTime[asset];
        if (lastUpdate == 0) {
            return true; // Never updated
        }

        return block.timestamp - lastUpdate >= updateInterval;
    }

    /**
     * @notice Get gas estimate for upkeep
     * @return gasEstimate Estimated gas needed for upkeep
     */
    function getUpkeepGasEstimate() external view returns (uint256 gasEstimate) {
        (bool needsUpdate_, address[] memory assets) = checkUpkeep();
        if (!needsUpdate_ || assets.length == 0) {
            return 0;
        }

        // Base gas + gas per asset update
        gasEstimate = 50000 + (assets.length * 30000) + gasBuffer;
    }
}

