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

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 "../../../dex/PrivatePoolRegistry.sol";
import "./IStablecoinPegManager.sol";
import "./ICommodityPegManager.sol";

/**
 * @title Minimal DODO PMM pool interface for Stabilizer swaps
 */
interface IDODOPMMPoolStabilizer {
    function _BASE_TOKEN_() external view returns (address);
    function _QUOTE_TOKEN_() external view returns (address);
    function sellBase(address to) external returns (uint256);
    function sellQuote(address to) external returns (uint256);
    function getMidPrice() external view returns (uint256);
}

/**
 * @title Stabilizer
 * @notice Phase 3: Deviation-triggered private swaps via XAU-anchored pools. Phase 6: TWAP/sustained deviation, per-block cap, flash containment.
 * @dev Implements Appendix A of VAULT_SYSTEM_MASTER_TECHNICAL_PLAN. Only STABILIZER_KEEPER_ROLE may call executePrivateSwap.
 */
contract Stabilizer is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

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

    PrivatePoolRegistry public immutable privatePoolRegistry;

    uint256 public lastExecutionBlock;
    uint256 public volumeThisBlock;
    uint256 public volumeBlockNumber;
    uint256 public minBlocksBetweenExecution = 3;
    uint256 public maxStabilizationVolumePerBlock;
    uint256 public thresholdBps = 50;
    uint256 public sustainedDeviationBlocks = 3;
    uint256 public maxSlippageBps = 100;
    uint256 public maxGasPriceForStabilizer;

    IStablecoinPegManager public stablecoinPegManager;
    address public stablecoinPegAsset;
    ICommodityPegManager public commodityPegManager;
    address public commodityPegAsset;
    bool public useStablecoinPeg; // true = use stablecoin peg, false = use commodity peg (when set)

    struct DeviationSample {
        uint256 blockNumber;
        int256 deviationBps;
    }
    uint256 public constant MAX_DEVIATION_SAMPLES = 32;
    DeviationSample[MAX_DEVIATION_SAMPLES] private _samples;
    uint256 private _sampleIndex;
    uint256 private _sampleCount;

    event PrivateSwapExecuted(address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);
    event ConfigUpdated(string key, uint256 value);
    event PegSourceStablecoinSet(address manager, address asset);
    event PegSourceCommoditySet(address manager, address asset);

    error NoPrivatePool();
    error ShouldNotRebalance();
    error BlockDelayNotMet();
    error VolumeCapExceeded();
    error SlippageExceeded();
    error GasPriceTooHigh();
    error ZeroAmount();
    error NoDeviationSource();
    error InsufficientBalance();

    constructor(address admin, address _privatePoolRegistry) {
        require(admin != address(0), "Stabilizer: zero admin");
        require(_privatePoolRegistry != address(0), "Stabilizer: zero registry");
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(STABILIZER_KEEPER_ROLE, admin);
        privatePoolRegistry = PrivatePoolRegistry(_privatePoolRegistry);
    }

    function setStablecoinPegSource(address manager, address asset) external onlyRole(DEFAULT_ADMIN_ROLE) {
        stablecoinPegManager = IStablecoinPegManager(manager);
        stablecoinPegAsset = asset;
        useStablecoinPeg = true;
        emit PegSourceStablecoinSet(manager, asset);
    }

    function setCommodityPegSource(address manager, address asset) external onlyRole(DEFAULT_ADMIN_ROLE) {
        commodityPegManager = ICommodityPegManager(manager);
        commodityPegAsset = asset;
        useStablecoinPeg = false;
        emit PegSourceCommoditySet(manager, asset);
    }

    function setMinBlocksBetweenExecution(uint256 v) external onlyRole(DEFAULT_ADMIN_ROLE) {
        minBlocksBetweenExecution = v;
        emit ConfigUpdated("minBlocksBetweenExecution", v);
    }

    function setMaxStabilizationVolumePerBlock(uint256 v) external onlyRole(DEFAULT_ADMIN_ROLE) {
        maxStabilizationVolumePerBlock = v;
        emit ConfigUpdated("maxStabilizationVolumePerBlock", v);
    }

    function setThresholdBps(uint256 v) external onlyRole(DEFAULT_ADMIN_ROLE) {
        thresholdBps = v;
        emit ConfigUpdated("thresholdBps", v);
    }

    function setSustainedDeviationBlocks(uint256 v) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(v <= MAX_DEVIATION_SAMPLES, "Stabilizer: sample limit");
        sustainedDeviationBlocks = v;
        emit ConfigUpdated("sustainedDeviationBlocks", v);
    }

    function setMaxSlippageBps(uint256 v) external onlyRole(DEFAULT_ADMIN_ROLE) {
        maxSlippageBps = v;
        emit ConfigUpdated("maxSlippageBps", v);
    }

    function setMaxGasPriceForStabilizer(uint256 v) external onlyRole(DEFAULT_ADMIN_ROLE) {
        maxGasPriceForStabilizer = v;
        emit ConfigUpdated("maxGasPriceForStabilizer", v);
    }

    /**
     * @notice Get current deviation in bps and whether rebalance is recommended (sustained over threshold).
     * @return deviationBps Current peg deviation in basis points.
     * @return shouldRebalance True if over threshold for sustainedDeviationBlocks samples.
     */
    function checkDeviation() external view returns (int256 deviationBps, bool shouldRebalance) {
        deviationBps = _getDeviationBps();
        // forge-lint: disable-next-line unsafe-typecast -- thresholdBps is uint256, casting to int256 for comparison with _abs result; thresholdBps is small (basis points)
        if (_abs(deviationBps) <= int256(uint256(thresholdBps))) {
            return (deviationBps, false);
        }
        shouldRebalance = _sustainedOverThreshold();
        return (deviationBps, shouldRebalance);
    }

    /**
     * @notice Record current deviation for sustained-deviation check (call from keeper each block or before executePrivateSwap).
     */
    function recordDeviation() external {
        int256 d = _getDeviationBps();
        _pushSample(block.number, d);
    }

    /**
     * @notice Execute a private swap via the private pool registry (only keeper when shouldRebalance).
     * @param tradeSize Amount of tokenIn to swap.
     * @param tokenIn Token to sell (must have balance on this contract).
     * @param tokenOut Token to buy.
     * @return amountOut Amount of tokenOut received (reverts on slippage or volume cap).
     */
    function executePrivateSwap(
        uint256 tradeSize,
        address tokenIn,
        address tokenOut
    ) external nonReentrant onlyRole(STABILIZER_KEEPER_ROLE) returns (uint256 amountOut) {
        if (tradeSize == 0) revert ZeroAmount();
        if (maxGasPriceForStabilizer != 0 && block.basefee > maxGasPriceForStabilizer) revert GasPriceTooHigh();
        if (block.number < lastExecutionBlock + minBlocksBetweenExecution) revert BlockDelayNotMet();

        _pushSample(block.number, _getDeviationBps());
        (int256 deviationBps, bool shouldRebalance) = this.checkDeviation();
        if (!shouldRebalance) revert ShouldNotRebalance();

        if (block.number != volumeBlockNumber) {
            volumeBlockNumber = block.number;
            volumeThisBlock = 0;
        }
        if (volumeThisBlock + tradeSize > maxStabilizationVolumePerBlock) revert VolumeCapExceeded();
        volumeThisBlock += tradeSize;
        lastExecutionBlock = block.number;

        address pool = privatePoolRegistry.getPrivatePool(tokenIn, tokenOut);
        if (pool == address(0)) revert NoPrivatePool();

        address base = IDODOPMMPoolStabilizer(pool)._BASE_TOKEN_();
        address quote = IDODOPMMPoolStabilizer(pool)._QUOTE_TOKEN_();
        uint256 midPrice = IDODOPMMPoolStabilizer(pool).getMidPrice();
        uint256 expectedOut = tokenIn == base
            ? (tradeSize * midPrice) / 1e18
            : (tradeSize * 1e18) / midPrice;
        uint256 minAmountOut = (expectedOut * (10000 - maxSlippageBps)) / 10000;

        if (IERC20(tokenIn).balanceOf(address(this)) < tradeSize) revert InsufficientBalance();
        IERC20(tokenIn).safeTransfer(pool, tradeSize);
        amountOut = tokenIn == base
            ? IDODOPMMPoolStabilizer(pool).sellBase(address(this))
            : IDODOPMMPoolStabilizer(pool).sellQuote(address(this));
        if (amountOut < minAmountOut) revert SlippageExceeded();

        emit PrivateSwapExecuted(tokenIn, tokenOut, tradeSize, amountOut);
        return amountOut;
    }

    function _getDeviationBps() internal view returns (int256) {
        if (useStablecoinPeg && address(stablecoinPegManager) != address(0) && stablecoinPegAsset != address(0)) {
            (, int256 d) = stablecoinPegManager.checkUSDpeg(stablecoinPegAsset);
            return d;
        }
        if (!useStablecoinPeg && address(commodityPegManager) != address(0) && commodityPegAsset != address(0)) {
            (, int256 d) = commodityPegManager.checkCommodityPeg(commodityPegAsset);
            return d;
        }
        return 0;
    }

    function _sustainedOverThreshold() internal view returns (bool) {
        if (sustainedDeviationBlocks == 0) return true;
        if (_sampleCount < sustainedDeviationBlocks) return false;
        uint256 n = sustainedDeviationBlocks;
        for (uint256 i = 0; i < n; i++) {
            uint256 idx = (_sampleIndex + MAX_DEVIATION_SAMPLES - 1 - i) % MAX_DEVIATION_SAMPLES;
            int256 d = _samples[idx].deviationBps;
            // forge-lint: disable-next-line unsafe-typecast -- d is deviationBps (small int256), safe to cast to uint256 for abs
            uint256 absD = d < 0 ? uint256(-d) : uint256(d);
            if (absD <= thresholdBps) return false;
        }
        return true;
    }

    function _pushSample(uint256 blockNum, int256 deviationBps) internal {
        uint256 idx = _sampleIndex % MAX_DEVIATION_SAMPLES;
        _samples[idx] = DeviationSample({ blockNumber: blockNum, deviationBps: deviationBps });
        _sampleIndex = ( _sampleIndex + 1) % MAX_DEVIATION_SAMPLES;
        if (_sampleCount < MAX_DEVIATION_SAMPLES) _sampleCount++;
    }

    function _abs(int256 x) internal pure returns (int256) {
        return x < 0 ? -x : x;
    }
}
