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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

interface IEngineXDodoPoolLike {
    function _BASE_TOKEN_() external view returns (address);
    function _QUOTE_TOKEN_() external view returns (address);
    function querySellBase(address trader, uint256 payBaseAmount)
        external
        view
        returns (uint256 receiveQuoteAmount, uint256 mtFee);
    function querySellQuote(address trader, uint256 payQuoteAmount)
        external
        view
        returns (uint256 receiveBaseAmount, uint256 mtFee);
    function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve);
}

interface IEngineXDodoIntegrationLike {
    function addLiquidity(address pool, uint256 baseAmount, uint256 quoteAmount)
        external
        returns (uint256 baseShare, uint256 quoteShare, uint256 lpShare);
}

/// @notice Engine X single-sided cWUSDC inventory wrapper for later DODO PMM promotion.
/// @dev cWUSDC-only inventory is accounting/support inventory, not executable public DODO liquidity.
///      DODO promotion is allowed only with nonzero base and quote amounts and passing canary guards.
contract DBISEngineXSingleSidedDodoCwusdcVault is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public immutable cWUSDC;
    IERC20 public immutable quoteToken;
    IEngineXDodoIntegrationLike public immutable dodoIntegration;

    address public dodoPool;
    bool public paused;

    uint256 public accountedCwusdcInventory;
    uint256 public accountedQuoteInventory;
    uint256 public totalCwusdcPromotedToDodo;
    uint256 public totalQuotePromotedToDodo;

    uint256 public sampleBaseIn;
    uint256 public minQuoteOut;
    uint256 public sampleQuoteIn;
    uint256 public minBaseOut;

    event Paused(address indexed operator);
    event Unpaused(address indexed operator);
    event DodoPoolUpdated(address indexed pool);
    event CanaryUpdated(uint256 sampleBaseIn, uint256 minQuoteOut, uint256 sampleQuoteIn, uint256 minBaseOut);
    event CwusdcInventoryDeposited(address indexed from, uint256 amount);
    event QuoteInventoryDeposited(address indexed from, uint256 amount);
    event InventoryWithdrawn(address indexed token, address indexed to, uint256 amount);
    event DodoLiquidityPromoted(
        address indexed pool,
        uint256 baseAmount,
        uint256 quoteAmount,
        uint256 baseShare,
        uint256 quoteShare,
        uint256 lpShare
    );
    event UnaccountedTokenWithdrawn(address indexed token, address indexed to, uint256 amount);

    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }

    constructor(address cWUSDC_, address quoteToken_, address dodoIntegration_, address owner_) Ownable(owner_) {
        require(cWUSDC_ != address(0) && quoteToken_ != address(0), "zero token");
        require(dodoIntegration_ != address(0), "zero integration");
        require(owner_ != address(0), "zero owner");
        require(cWUSDC_ != quoteToken_, "same token");
        cWUSDC = IERC20(cWUSDC_);
        quoteToken = IERC20(quoteToken_);
        dodoIntegration = IEngineXDodoIntegrationLike(dodoIntegration_);
    }

    function pause() external onlyOwner {
        paused = true;
        emit Paused(msg.sender);
    }

    function unpause() external onlyOwner {
        paused = false;
        emit Unpaused(msg.sender);
    }

    function setDodoPool(address pool) external onlyOwner {
        _validatePool(pool);
        dodoPool = pool;
        emit DodoPoolUpdated(pool);
    }

    function setCanary(uint256 sampleBaseIn_, uint256 minQuoteOut_, uint256 sampleQuoteIn_, uint256 minBaseOut_)
        external
        onlyOwner
    {
        require(sampleBaseIn_ > 0 || sampleQuoteIn_ > 0, "zero canary");
        require((sampleBaseIn_ == 0) == (minQuoteOut_ == 0), "base canary mismatch");
        require((sampleQuoteIn_ == 0) == (minBaseOut_ == 0), "quote canary mismatch");
        sampleBaseIn = sampleBaseIn_;
        minQuoteOut = minQuoteOut_;
        sampleQuoteIn = sampleQuoteIn_;
        minBaseOut = minBaseOut_;
        emit CanaryUpdated(sampleBaseIn_, minQuoteOut_, sampleQuoteIn_, minBaseOut_);
    }

    function depositCwusdc(uint256 amount) external nonReentrant whenNotPaused {
        require(amount > 0, "zero deposit");
        cWUSDC.safeTransferFrom(msg.sender, address(this), amount);
        accountedCwusdcInventory += amount;
        emit CwusdcInventoryDeposited(msg.sender, amount);
    }

    function depositQuote(uint256 amount) external nonReentrant whenNotPaused {
        require(amount > 0, "zero deposit");
        quoteToken.safeTransferFrom(msg.sender, address(this), amount);
        accountedQuoteInventory += amount;
        emit QuoteInventoryDeposited(msg.sender, amount);
    }

    function withdrawCwusdcInventory(address to, uint256 amount) external onlyOwner nonReentrant {
        require(to != address(0), "zero to");
        require(amount > 0, "zero withdraw");
        require(amount <= accountedCwusdcInventory, "insufficient cwusdc inventory");
        accountedCwusdcInventory -= amount;
        cWUSDC.safeTransfer(to, amount);
        _requireSolvent();
        emit InventoryWithdrawn(address(cWUSDC), to, amount);
    }

    function withdrawQuoteInventory(address to, uint256 amount) external onlyOwner nonReentrant {
        require(to != address(0), "zero to");
        require(amount > 0, "zero withdraw");
        require(amount <= accountedQuoteInventory, "insufficient quote inventory");
        accountedQuoteInventory -= amount;
        quoteToken.safeTransfer(to, amount);
        _requireSolvent();
        emit InventoryWithdrawn(address(quoteToken), to, amount);
    }

    function promoteToDodo(
        uint256 baseAmount,
        uint256 quoteAmount,
        uint256 minBaseShare,
        uint256 minQuoteShare,
        uint256 minLpShare
    ) external onlyOwner nonReentrant whenNotPaused returns (uint256 baseShare, uint256 quoteShare, uint256 lpShare) {
        require(dodoPool != address(0), "pool not set");
        require(baseAmount > 0 && quoteAmount > 0, "two-sided required");
        require(baseAmount <= accountedCwusdcInventory, "insufficient cwusdc inventory");
        require(quoteAmount <= accountedQuoteInventory, "insufficient quote inventory");

        accountedCwusdcInventory -= baseAmount;
        accountedQuoteInventory -= quoteAmount;

        cWUSDC.forceApprove(address(dodoIntegration), baseAmount);
        quoteToken.forceApprove(address(dodoIntegration), quoteAmount);
        (baseShare, quoteShare, lpShare) = dodoIntegration.addLiquidity(dodoPool, baseAmount, quoteAmount);
        cWUSDC.forceApprove(address(dodoIntegration), 0);
        quoteToken.forceApprove(address(dodoIntegration), 0);

        require(baseShare >= minBaseShare, "base share too low");
        require(quoteShare >= minQuoteShare, "quote share too low");
        require(lpShare >= minLpShare, "lp share too low");
        require(canaryPasses(), "canary failed");

        totalCwusdcPromotedToDodo += baseAmount;
        totalQuotePromotedToDodo += quoteAmount;
        _requireSolvent();
        emit DodoLiquidityPromoted(dodoPool, baseAmount, quoteAmount, baseShare, quoteShare, lpShare);
    }

    function canaryPasses() public view returns (bool) {
        if (dodoPool == address(0)) return false;
        IEngineXDodoPoolLike pool = IEngineXDodoPoolLike(dodoPool);
        (uint256 baseReserve, uint256 quoteReserve) = pool.getVaultReserve();
        if (baseReserve == 0 || quoteReserve == 0) return false;
        if (sampleBaseIn > 0) {
            (uint256 quoteOut,) = pool.querySellBase(address(this), sampleBaseIn);
            if (quoteOut < minQuoteOut) return false;
        }
        if (sampleQuoteIn > 0) {
            (uint256 baseOut,) = pool.querySellQuote(address(this), sampleQuoteIn);
            if (baseOut < minBaseOut) return false;
        }
        return true;
    }

    function solvencyState()
        external
        view
        returns (
            uint256 cwusdcBalance,
            uint256 quoteBalance,
            uint256 cwusdcInventory,
            uint256 quoteInventory,
            bool solvent,
            bool executable
        )
    {
        cwusdcBalance = cWUSDC.balanceOf(address(this));
        quoteBalance = quoteToken.balanceOf(address(this));
        cwusdcInventory = accountedCwusdcInventory;
        quoteInventory = accountedQuoteInventory;
        solvent = cwusdcBalance >= cwusdcInventory && quoteBalance >= quoteInventory;
        executable = canaryPasses();
    }

    function rescueUnaccountedToken(address token, address to, uint256 amount) external onlyOwner nonReentrant {
        require(to != address(0), "zero to");
        require(amount > 0, "zero withdraw");
        IERC20(token).safeTransfer(to, amount);
        _requireSolvent();
        emit UnaccountedTokenWithdrawn(token, to, amount);
    }

    function _validatePool(address pool) internal view {
        require(pool != address(0), "zero pool");
        require(IEngineXDodoPoolLike(pool)._BASE_TOKEN_() == address(cWUSDC), "unexpected base");
        require(IEngineXDodoPoolLike(pool)._QUOTE_TOKEN_() == address(quoteToken), "unexpected quote");
    }

    function _requireSolvent() internal view {
        require(cWUSDC.balanceOf(address(this)) >= accountedCwusdcInventory, "cwusdc insolvent");
        require(quoteToken.balanceOf(address(this)) >= accountedQuoteInventory, "quote insolvent");
    }
}
