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

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

interface IEngineXUniswapV3PoolLike {
    function token0() external view returns (address);
    function token1() external view returns (address);
    function fee() external view returns (uint24);
    function liquidity() external view returns (uint128);

    function slot0()
        external
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext,
            uint8 feeProtocol,
            bool unlocked
        );
}

/// @notice Public indexed-liquidity proof anchor for Engine X.
/// @dev This contract does not claim virtual DEX volume. It anchors Engine X proof IDs to a public UniV3 pool state.
contract DBISEngineXIndexedLiquidityVault is Ownable {
    address public immutable cWUSDC;
    address public immutable usdc;
    IEngineXUniswapV3PoolLike public immutable pool;
    uint24 public immutable fee;

    uint256 public positionTokenId;
    int24 public maxAbsTick;
    uint128 public minLiquidity;
    uint256 public minToken0Balance;
    uint256 public minToken1Balance;
    uint256 public maxProofSwapAmount;
    bool public paused;
    bool public operatorAllowlistEnabled;

    mapping(address => bool) public approvedOperator;
    mapping(bytes32 => bool) public usedProofIds;

    struct IndexedProof {
        bytes32 proofId;
        bytes32 publicSwapTxHash;
        bytes32 liquidityTxHash;
        address outputRecipient;
        uint256 exactOutputAmount;
        bytes32 iso20022DocumentHash;
        bytes32 auditEnvelopeHash;
        bytes32 pegProofHash;
    }

    event RiskControlsUpdated(
        int24 maxAbsTick,
        uint128 minLiquidity,
        uint256 minToken0Balance,
        uint256 minToken1Balance,
        uint256 maxProofSwapAmount
    );
    event PositionTokenIdUpdated(uint256 positionTokenId);
    event Paused(address indexed operator);
    event Unpaused(address indexed operator);
    event OperatorAllowlistEnabledUpdated(bool enabled);
    event OperatorApprovalUpdated(address indexed operator, bool approved);
    event IndexedLiquidityProof(
        bytes32 indexed proofId,
        address indexed operator,
        address indexed outputRecipient,
        address pool,
        uint24 fee,
        uint256 positionTokenId,
        uint160 sqrtPriceX96,
        int24 tick,
        uint128 liquidity,
        uint256 token0Balance,
        uint256 token1Balance,
        uint256 exactOutputAmount,
        bytes32 publicSwapTxHash,
        bytes32 liquidityTxHash,
        bytes32 iso20022DocumentHash,
        bytes32 auditEnvelopeHash,
        bytes32 pegProofHash
    );

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

    modifier onlyApprovedOperator() {
        require(!operatorAllowlistEnabled || approvedOperator[msg.sender], "operator not approved");
        _;
    }

    constructor(
        address cWUSDC_,
        address usdc_,
        address pool_,
        address owner_,
        int24 maxAbsTick_,
        uint128 minLiquidity_,
        uint256 maxProofSwapAmount_
    ) Ownable(owner_) {
        require(cWUSDC_ != address(0) && usdc_ != address(0) && pool_ != address(0), "zero address");
        require(owner_ != address(0), "zero owner");
        cWUSDC = cWUSDC_;
        usdc = usdc_;
        pool = IEngineXUniswapV3PoolLike(pool_);

        address token0 = pool.token0();
        address token1 = pool.token1();
        require((token0 == cWUSDC_ && token1 == usdc_) || (token0 == usdc_ && token1 == cWUSDC_), "pool token mismatch");

        fee = pool.fee();
        _setRiskControls(maxAbsTick_, minLiquidity_, 0, 0, maxProofSwapAmount_);
    }

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

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

    function setOperatorAllowlistEnabled(bool enabled) external onlyOwner {
        operatorAllowlistEnabled = enabled;
        emit OperatorAllowlistEnabledUpdated(enabled);
    }

    function setOperatorApproved(address operator, bool approved) external onlyOwner {
        require(operator != address(0), "zero operator");
        approvedOperator[operator] = approved;
        emit OperatorApprovalUpdated(operator, approved);
    }

    function setPositionTokenId(uint256 positionTokenId_) external onlyOwner {
        positionTokenId = positionTokenId_;
        emit PositionTokenIdUpdated(positionTokenId_);
    }

    function setRiskControls(
        int24 maxAbsTick_,
        uint128 minLiquidity_,
        uint256 minToken0Balance_,
        uint256 minToken1Balance_,
        uint256 maxProofSwapAmount_
    ) external onlyOwner {
        _setRiskControls(maxAbsTick_, minLiquidity_, minToken0Balance_, minToken1Balance_, maxProofSwapAmount_);
    }

    function currentPoolState()
        public
        view
        returns (
            uint160 sqrtPriceX96,
            int24 tick,
            uint128 activeLiquidity,
            uint256 token0Balance,
            uint256 token1Balance
        )
    {
        (sqrtPriceX96, tick,,,,,) = pool.slot0();
        activeLiquidity = pool.liquidity();
        token0Balance = IERC20(pool.token0()).balanceOf(address(pool));
        token1Balance = IERC20(pool.token1()).balanceOf(address(pool));
    }

    function recordIndexedProof(IndexedProof calldata proof)
        external
        whenNotPaused
        onlyApprovedOperator
        returns (uint160 sqrtPriceX96, int24 tick, uint128 activeLiquidity)
    {
        require(proof.proofId != bytes32(0), "zero proof");
        require(!usedProofIds[proof.proofId], "proof used");
        require(proof.publicSwapTxHash != bytes32(0), "zero swap hash");
        require(proof.liquidityTxHash != bytes32(0), "zero liquidity hash");
        require(proof.outputRecipient != address(0), "zero recipient");
        require(proof.exactOutputAmount > 0, "zero output");
        require(maxProofSwapAmount == 0 || proof.exactOutputAmount <= maxProofSwapAmount, "proof amount too high");
        require(proof.iso20022DocumentHash != bytes32(0), "zero iso hash");
        require(proof.auditEnvelopeHash != bytes32(0), "zero audit hash");
        require(proof.pegProofHash != bytes32(0), "zero peg hash");

        uint256 token0Balance;
        uint256 token1Balance;
        (sqrtPriceX96, tick, activeLiquidity, token0Balance, token1Balance) = currentPoolState();
        require(_absTick(tick) <= uint24(maxAbsTick), "tick drift too high");
        require(activeLiquidity >= minLiquidity, "insufficient liquidity");
        require(token0Balance >= minToken0Balance, "insufficient token0 balance");
        require(token1Balance >= minToken1Balance, "insufficient token1 balance");

        usedProofIds[proof.proofId] = true;
        emit IndexedLiquidityProof(
            proof.proofId,
            msg.sender,
            proof.outputRecipient,
            address(pool),
            fee,
            positionTokenId,
            sqrtPriceX96,
            tick,
            activeLiquidity,
            token0Balance,
            token1Balance,
            proof.exactOutputAmount,
            proof.publicSwapTxHash,
            proof.liquidityTxHash,
            proof.iso20022DocumentHash,
            proof.auditEnvelopeHash,
            proof.pegProofHash
        );
    }

    function _setRiskControls(
        int24 maxAbsTick_,
        uint128 minLiquidity_,
        uint256 minToken0Balance_,
        uint256 minToken1Balance_,
        uint256 maxProofSwapAmount_
    ) internal {
        require(maxAbsTick_ >= 0, "negative tick gate");
        maxAbsTick = maxAbsTick_;
        minLiquidity = minLiquidity_;
        minToken0Balance = minToken0Balance_;
        minToken1Balance = minToken1Balance_;
        maxProofSwapAmount = maxProofSwapAmount_;
        emit RiskControlsUpdated(maxAbsTick_, minLiquidity_, minToken0Balance_, minToken1Balance_, maxProofSwapAmount_);
    }

    function _absTick(int24 tick) internal pure returns (uint24) {
        return tick >= 0 ? uint24(tick) : uint24(-tick);
    }
}
