// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.19;

import "@openzeppelin/utils/math/SignedMath.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";

/**
 * @title PriceOracle
 * @notice Abstract contract providing price stability validation using Uniswap V3 TWAP oracle
 * @dev Contains oracle-related functionality for validating price movements and stability
 */
abstract contract PriceOracle {
    /// @notice Interval for price stability checks (30 seconds)
    /// @dev Reduced from 300s (5 min) to 30s to align with MIN_RECENTER_INTERVAL (60s).
    ///      The old 300s window caused recenter to revert with "price deviated from oracle"
    ///      for ~285s after any significant trade (> ~1000-tick move), creating a window
    ///      where the LiquidityManager could not reposition and an adversary with parasitic
    ///      LP could extract value from passive holders (issue #517).
    ///      30s still prevents same-block manipulation on Ethereum mainnet (~12s block time)
    ///      while ensuring TWAP converges well within the 60s cooldown period.
    uint32 internal constant PRICE_STABILITY_INTERVAL = 30;
    /// @notice Maximum allowed tick deviation from TWAP average
    int24 internal constant MAX_TICK_DEVIATION = 50;

    /// @notice The Uniswap V3 pool used for oracle data
    function _getPool() internal view virtual returns (IUniswapV3Pool);

    /// @notice Validates if the current price is stable compared to TWAP oracle
    /// @param currentTick The current tick to validate
    /// @return isStable True if price is within acceptable deviation from TWAP
    function _isPriceStable(int24 currentTick) internal view returns (bool isStable) {
        IUniswapV3Pool pool = _getPool();

        uint32[] memory secondsAgo = new uint32[](2);
        secondsAgo[0] = PRICE_STABILITY_INTERVAL; // 30 seconds ago
        secondsAgo[1] = 0; // current block timestamp

        int24 averageTick;
        try pool.observe(secondsAgo) returns (int56[] memory tickCumulatives, uint160[] memory) {
            int56 tickCumulativeDiff = tickCumulatives[1] - tickCumulatives[0];
            averageTick = int24(tickCumulativeDiff / int56(int32(PRICE_STABILITY_INTERVAL)));
        } catch {
            // Fallback to longer timeframe if recent data unavailable
            uint32 fallbackInterval = PRICE_STABILITY_INTERVAL * 200; // 6,000 seconds
            secondsAgo[0] = fallbackInterval;
            try pool.observe(secondsAgo) returns (int56[] memory fallbackCumulatives, uint160[] memory) {
                int56 tickCumulativeDiff = fallbackCumulatives[1] - fallbackCumulatives[0];
                averageTick = int24(tickCumulativeDiff / int56(int32(fallbackInterval)));
            } catch {
                // Pool has insufficient observation history for both intervals.
                // Treat price as unstable (safe default) — prevents recenter() from
                // reverting with an opaque Uniswap error on pools with very short history.
                return false;
            }
        }

        isStable = (currentTick >= averageTick - MAX_TICK_DEVIATION && currentTick <= averageTick + MAX_TICK_DEVIATION);
    }

    /// @notice Validates if price movement is sufficient for recentering
    /// @param currentTick The current market tick
    /// @param centerTick The center tick of the anchor position
    /// @param tickSpacing The tick spacing for minimum amplitude calculation
    /// @param token0isWeth Whether token0 is WETH (affects price direction logic)
    /// @return isUp True if ETH price moved up (WETH appreciating vs KRK); accounts for token0isWeth ordering
    /// @return isEnough True if movement amplitude is sufficient for recentering
    function _validatePriceMovement(int24 currentTick, int24 centerTick, int24 tickSpacing, bool token0isWeth)
        internal
        pure
        returns (bool isUp, bool isEnough)
    {
        uint256 minAmplitude = uint24(tickSpacing) * 2;

        // Determine the correct comparison direction based on token0isWeth
        isUp = token0isWeth ? currentTick < centerTick : currentTick > centerTick;
        isEnough = SignedMath.abs(currentTick - centerTick) > minAmplitude;
    }
}
