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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./TreasuryVault.sol";
import "./CcipBridgeAdapter138.sol";

/**
 * @title StrategyExecutor138
 * @notice Single "brain" that can request moves from TreasuryVault and initiate export via CcipBridgeAdapter138.
 * @dev Token allowlist = canonical 138 list; no calldata-provided token/receiver for export. See docs/treasury/EXECUTOR_ALLOWLIST_MATRIX.md.
 */
contract StrategyExecutor138 is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /// @notice Export trigger mode: daily sweep, threshold-based, or hybrid.
    enum ExportMode {
        Daily,
        Threshold,
        Hybrid
    }

    /// @notice Policy for export (caps enforced by TreasuryVault; mode/minExportUsd for bot logic).
    struct ExportPolicy {
        ExportMode mode;
        uint256 minExportUsd;
        uint256 maxPerTxUsd;
        uint256 maxDailyUsd;
        uint256 rateLimitPerHour;
        uint256 cooldownBlocks;
        address exportAsset;
        uint64 destinationSelector;
        address destinationReceiver;
    }

    bytes32 public constant KEEPER_ROLE = keccak256("KEEPER_ROLE");

    TreasuryVault public immutable vault;
    CcipBridgeAdapter138 public immutable ccipAdapter;

    mapping(address => bool) public allowedTokens;
    mapping(address => bool) public allowedRoutersOrLp;

    uint256 public cooldownBlocks;
    uint256 public lastExportBlock;

    ExportPolicy public exportPolicy;

    uint256 public pendingIntentAmount;
    address public pendingIntentToken;

    event ExportToMainnetRequested(uint256 amount, uint256 deadline);
    event CooldownBlocksSet(uint256 blocks);
    event ExportPolicySet(ExportMode mode, uint256 minExportUsd);
    event ExportIntentRecorded(address indexed token, uint256 amount);
    event PendingIntentProcessed(uint256 amount);

    error TokenNotApproved();
    error RouterNotApproved();
    error CooldownNotElapsed();
    error ZeroAddress();
    error NoPendingIntent();
    error ExportsNotEnabled();
    error NotImplemented();

    constructor(
        address _vault,
        address _ccipAdapter,
        address admin
    ) {
        if (_vault == address(0) || _ccipAdapter == address(0)) revert ZeroAddress();
        vault = TreasuryVault(payable(_vault));
        ccipAdapter = CcipBridgeAdapter138(payable(_ccipAdapter));
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(KEEPER_ROLE, admin);
    }

    function setToken(address token, bool approved) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (token == address(0)) revert ZeroAddress();
        allowedTokens[token] = approved;
    }

    function setRouterOrLp(address routerOrLp, bool approved) external onlyRole(DEFAULT_ADMIN_ROLE) {
        if (routerOrLp == address(0)) revert ZeroAddress();
        allowedRoutersOrLp[routerOrLp] = approved;
    }

    function setCooldownBlocks(uint256 blocks) external onlyRole(DEFAULT_ADMIN_ROLE) {
        cooldownBlocks = blocks;
        emit CooldownBlocksSet(blocks);
    }

    function setExportPolicy(ExportPolicy calldata policy) external onlyRole(DEFAULT_ADMIN_ROLE) {
        exportPolicy = policy;
        if (policy.cooldownBlocks != cooldownBlocks) {
            cooldownBlocks = policy.cooldownBlocks;
            emit CooldownBlocksSet(policy.cooldownBlocks);
        }
        emit ExportPolicySet(policy.mode, policy.minExportUsd);
    }

    /**
     * @notice Record intent to export when CCIP is not yet live. Process later with processPendingIntent().
     * Only one pending intent (overwrites previous). Call when ccipAdapter.exportsEnabled() is false.
     */
    function recordExportIntent(address token, uint256 amount) external onlyRole(KEEPER_ROLE) {
        if (ccipAdapter.exportsEnabled()) revert ExportsNotEnabled();
        if (token == address(0)) revert ZeroAddress();
        if (!allowedTokens[token]) revert TokenNotApproved();
        pendingIntentToken = token;
        pendingIntentAmount = amount;
        emit ExportIntentRecorded(token, amount);
    }

    /**
     * @notice Process the pending export intent once CCIP exports are enabled.
     */
    function processPendingIntent(uint256 deadline)
        external
        payable
        nonReentrant
        onlyRole(KEEPER_ROLE)
    {
        if (!ccipAdapter.exportsEnabled()) revert ExportsNotEnabled();
        if (pendingIntentToken == address(0) || pendingIntentAmount == 0) revert NoPendingIntent();
        address token = pendingIntentToken;
        uint256 amount = pendingIntentAmount;
        pendingIntentToken = address(0);
        pendingIntentAmount = 0;
        if (block.timestamp > deadline) revert();
        if (cooldownBlocks != 0 && block.number < lastExportBlock + cooldownBlocks) revert CooldownNotElapsed();
        lastExportBlock = block.number;
        vault.requestTransfer(token, amount, address(this));
        IERC20(token).approve(address(ccipAdapter), amount);
        ccipAdapter.sendWeth9ToMainnet{value: msg.value}(amount, 0, deadline);
        emit ExportToMainnetRequested(amount, deadline);
        emit PendingIntentProcessed(amount);
    }

    /**
     * @notice Harvest fees from LP/router. Stub for bot integration; implement when LP contracts are wired.
     */
    function harvestFees() external view onlyRole(KEEPER_ROLE) {
        revert NotImplemented();
    }

    /**
     * @notice Rebalance LP positions. Stub for bot integration; implement when LP contracts are wired.
     */
    function rebalanceLp() external view onlyRole(KEEPER_ROLE) {
        revert NotImplemented();
    }

    /**
     * @notice Request WETH9 from vault and send to mainnet via CCIP. Only allowed WETH9; no calldata destinations.
     * @param weth9Amount Amount of WETH9 to export.
     * @param deadline Revert if block.timestamp > deadline.
     */
    function exportToMainnet(address weth9Token, uint256 weth9Amount, uint256 deadline)
        external
        payable
        nonReentrant
        onlyRole(KEEPER_ROLE)
    {
        if (weth9Token == address(0)) revert ZeroAddress();
        if (!allowedTokens[weth9Token]) revert TokenNotApproved();
        if (block.timestamp > deadline) revert();
        if (cooldownBlocks != 0 && block.number < lastExportBlock + cooldownBlocks) revert CooldownNotElapsed();

        lastExportBlock = block.number;

        vault.requestTransfer(weth9Token, weth9Amount, address(this));
        IERC20(weth9Token).approve(address(ccipAdapter), weth9Amount);

        ccipAdapter.sendWeth9ToMainnet{value: msg.value}(weth9Amount, 0, deadline);
        emit ExportToMainnetRequested(weth9Amount, deadline);
    }
}
