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

import "../../src/abstracts/ThreePositionStrategy.sol";
import "../helpers/TestBase.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import "forge-std/Test.sol";

/**
 * @title ThreePositionStrategy Test Suite
 * @notice Unit tests for the anti-arbitrage three-position strategy (Floor, Anchor, Discovery)
 */

// Mock implementation for testing ThreePositionStrategy
contract MockThreePositionStrategy is ThreePositionStrategy {
    address public harbToken;
    address public wethToken;
    bool public token0IsWeth;
    uint256 public ethBalance;
    uint256 public outstandingSupply;

    // Track minted positions for testing
    struct MintedPosition {
        Stage stage;
        int24 tickLower;
        int24 tickUpper;
        uint128 liquidity;
    }

    MintedPosition[] public mintedPositions;

    constructor(address _harbToken, address _wethToken, bool _token0IsWeth, uint256 _ethBalance, uint256 _outstandingSupply) {
        harbToken = _harbToken;
        wethToken = _wethToken;
        token0IsWeth = _token0IsWeth;
        ethBalance = _ethBalance;
        outstandingSupply = _outstandingSupply;
    }

    // Test helper functions
    function setEthBalance(uint256 _ethBalance) external {
        ethBalance = _ethBalance;
    }

    function setOutstandingSupply(uint256 _outstandingSupply) external {
        outstandingSupply = _outstandingSupply;
    }

    function setVWAP(uint256 vwapX96, uint256 volume) external {
        // Mock VWAP data for testing
        cumulativeVolumeWeightedPriceX96 = vwapX96 * volume;
        cumulativeVolume = volume;
    }

    function clearMintedPositions() external {
        delete mintedPositions;
    }

    function getMintedPositionsCount() external view returns (uint256) {
        return mintedPositions.length;
    }

    function getMintedPosition(uint256 index) external view returns (MintedPosition memory) {
        return mintedPositions[index];
    }

    // Expose internal functions for testing
    function setPositions(int24 currentTick, PositionParams memory params) external {
        _setPositions(currentTick, params);
    }

    function setAnchorPosition(int24 currentTick, uint256 anchorEthBalance, PositionParams memory params) external returns (uint256, uint128) {
        return _setAnchorPosition(currentTick, anchorEthBalance, params);
    }

    function setDiscoveryPosition(int24 currentTick, uint128 anchorLiquidity, PositionParams memory params) external returns (uint256) {
        return _setDiscoveryPosition(currentTick, anchorLiquidity, params);
    }

    function setFloorPosition(int24 currentTick, uint256 floorEthBalance, uint256 pulledHarb, uint256 discoveryAmount, PositionParams memory params) external {
        _setFloorPosition(currentTick, floorEthBalance, pulledHarb, discoveryAmount, params);
    }

    // Implementation of abstract functions
    function _getKraikenToken() internal view override returns (address) {
        return harbToken;
    }

    function _getWethToken() internal view override returns (address) {
        return wethToken;
    }

    function _isToken0Weth() internal view override returns (bool) {
        return token0IsWeth;
    }

    function _mintPosition(Stage stage, int24 tickLower, int24 tickUpper, uint128 liquidity) internal override {
        positions[stage] = TokenPosition({ liquidity: liquidity, tickLower: tickLower, tickUpper: tickUpper });

        mintedPositions.push(MintedPosition({ stage: stage, tickLower: tickLower, tickUpper: tickUpper, liquidity: liquidity }));
    }

    function _getEthBalance() internal view override returns (uint256) {
        return ethBalance;
    }

    function _getOutstandingSupply() internal view override returns (uint256) {
        return outstandingSupply;
    }
}

contract ThreePositionStrategyTest is TestConstants {
    MockThreePositionStrategy internal strategy;

    address internal constant HARB_TOKEN = address(0x1234);
    address internal constant WETH_TOKEN = address(0x5678);

    // Default test parameters
    int24 internal constant CURRENT_TICK = 0;
    uint256 internal constant ETH_BALANCE = 100 ether;
    uint256 internal constant OUTSTANDING_SUPPLY = 1_000_000 ether;

    function setUp() public {
        strategy = new MockThreePositionStrategy(
            HARB_TOKEN,
            WETH_TOKEN,
            true, // token0IsWeth
            ETH_BALANCE,
            OUTSTANDING_SUPPLY
        );
    }

    // Using getDefaultParams() from TestBase

    // ========================================
    // ANCHOR POSITION TESTS
    // ========================================

    function testAnchorPositionBasic() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        uint256 anchorEthBalance = 20 ether; // 20% of total

        (uint256 pulledHarb,) = strategy.setAnchorPosition(CURRENT_TICK, anchorEthBalance, params);

        // Verify position was created
        assertEq(strategy.getMintedPositionsCount(), 1, "Should have minted one position");

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.ANCHOR), "Should be anchor position");
        assertGt(pos.liquidity, 0, "Liquidity should be positive");
        assertGt(pulledHarb, 0, "Should pull some HARB tokens");

        // Verify tick range is reasonable
        int24 expectedSpacing = 200 + (34 * 50 * 200 / 100); // TICK_SPACING + anchorWidth calculation
        assertEq(pos.tickUpper - pos.tickLower, expectedSpacing * 2, "Tick range should match anchor spacing");
    }

    function testAnchorPositionSymmetricAroundCurrentTick() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        uint256 anchorEthBalance = 20 ether;

        strategy.setAnchorPosition(CURRENT_TICK, anchorEthBalance, params);
        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);

        // Position should be symmetric around current tick
        int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
        int24 normalizedCurrentTick = CURRENT_TICK / 200 * 200; // Normalize to tick spacing

        assertApproxEqAbs(uint256(int256(centerTick)), uint256(int256(normalizedCurrentTick)), 200, "Anchor should be centered around current tick");
    }

    function testAnchorPositionWidthScaling() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.anchorWidth = 100; // Maximum width
        uint256 anchorEthBalance = 20 ether;

        strategy.setAnchorPosition(CURRENT_TICK, anchorEthBalance, params);
        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);

        // Calculate expected spacing for 100% width
        int24 expectedSpacing = 200 + (34 * 100 * 200 / 100); // Should be 7000
        assertEq(pos.tickUpper - pos.tickLower, expectedSpacing * 2, "Width should scale with anchorWidth parameter");
    }

    // ========================================
    // DISCOVERY POSITION TESTS
    // ========================================

    function testDiscoveryPositionDependsOnAnchor() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        uint128 anchorLiquidity = 1000e18; // Simulated anchor liquidity

        uint256 discoveryAmount = strategy.setDiscoveryPosition(CURRENT_TICK, anchorLiquidity, params);

        // Discovery amount should be proportional to anchor liquidity
        assertGt(discoveryAmount, 0, "Discovery amount should be positive");

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.DISCOVERY), "Should be discovery position");

        // Discovery liquidity should ensure multiple times more liquidity per tick
        uint256 expectedMultiplier = 200 + (800 * params.discoveryDepth / 10 ** 18);
        // Calculate anchor width (same calculation as in _setDiscoveryPosition)
        int24 anchorSpacing = 200 + (34 * int24(params.anchorWidth) * 200 / 100);
        int24 anchorWidth = 2 * anchorSpacing;
        // Adjust for width difference
        uint128 expectedLiquidity = uint128(uint256(anchorLiquidity) * expectedMultiplier * 11_000 / (100 * uint256(int256(anchorWidth))));
        assertEq(pos.liquidity, expectedLiquidity, "Discovery liquidity should match expected multiple adjusted for width");
    }

    function testDiscoveryPositionPlacement() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        bool token0IsWeth = true;

        // Test with WETH as token0
        strategy = new MockThreePositionStrategy(HARB_TOKEN, WETH_TOKEN, token0IsWeth, ETH_BALANCE, OUTSTANDING_SUPPLY);

        uint128 anchorLiquidity = 1000e18;
        strategy.setDiscoveryPosition(CURRENT_TICK, anchorLiquidity, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);

        // When WETH is token0, discovery should be positioned below current price
        // (covering the range where HARB gets cheaper)
        assertLt(pos.tickUpper, CURRENT_TICK, "Discovery should be below current tick when WETH is token0");
    }

    function testDiscoveryDepthScaling() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.discoveryDepth = 10 ** 18; // Maximum depth (100%)

        uint128 anchorLiquidity = 1000e18;
        uint256 discoveryAmount1 = strategy.setDiscoveryPosition(CURRENT_TICK, anchorLiquidity, params);

        strategy.clearMintedPositions();
        params.discoveryDepth = 0; // Minimum depth
        uint256 discoveryAmount2 = strategy.setDiscoveryPosition(CURRENT_TICK, anchorLiquidity, params);

        assertGt(discoveryAmount1, discoveryAmount2, "Higher discovery depth should result in more tokens");
    }

    // ========================================
    // FLOOR POSITION TESTS
    // ========================================

    function testFloorPositionUsesVWAP() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        // Set up VWAP data
        uint256 vwapX96 = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96 format
        strategy.setVWAP(vwapX96, 1000 ether);

        uint256 floorEthBalance = 80 ether;
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.FLOOR), "Should be floor position");

        // Floor position should not be at current tick (should use VWAP)
        int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
        assertNotEq(centerTick, CURRENT_TICK, "Floor should not be positioned at current tick when VWAP available");
    }

    function testFloorPositionScarcityDominates() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        // Set up scenario where ETH is insufficient → scarcity tick dominates
        uint256 vwapX96 = 79_228_162_514_264_337_593_543_950_336 * 10; // High VWAP price
        strategy.setVWAP(vwapX96, 1000 ether);

        uint256 smallEthBalance = 1 ether; // Very low ETH → scarcity tick far away
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        // Should not revert — floor placed using max(scarcity, mirror, clamp)
        strategy.setFloorPosition(CURRENT_TICK, smallEthBalance, pulledHarb, discoveryAmount, params);

        // Floor should be minted (position exists)
        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertTrue(pos.liquidity > 0, "Floor should have liquidity");
    }

    function testFloorPositionMirrorDominates() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        // Set up scenario where VWAP is far from current → mirror tick dominates
        uint256 baseVwap = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96 format
        uint256 vwapX96 = baseVwap / 100_000; // Very low VWAP price → far from current
        strategy.setVWAP(vwapX96, 1000 ether);

        uint256 largeEthBalance = 100_000 ether; // Lots of ETH
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        strategy.setFloorPosition(CURRENT_TICK, largeEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertTrue(pos.liquidity > 0, "Floor should have liquidity");

        // Floor should be further than just anchorSpacing (mirror should push it)
        int24 anchorSpacing = 200 + (34 * 50 * 200 / 100); // 3600
        int24 floorCenter = (pos.tickLower + pos.tickUpper) / 2;
        // Mirror should push floor significantly beyond clamp minimum
        assertTrue(floorCenter > CURRENT_TICK + anchorSpacing + 200, "Mirror should push floor beyond clamp minimum");
    }

    function testFloorPositionNoVWAP() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        // No VWAP data (volume = 0)
        strategy.setVWAP(0, 0);

        uint256 floorEthBalance = 80 ether;
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);

        // Without VWAP, mirror = current tick, so floor uses max(scarcity, clamp)
        // With these balances, scarcity tick should dominate (low ETH relative to supply)
        int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
        // Floor should be above current tick (on KRK-cheap side)
        assertTrue(centerTick > CURRENT_TICK, "Floor should be on KRK-cheap side of current tick");
        assertTrue(pos.liquidity > 0, "Floor should have liquidity");
    }

    function testFloorPositionNoVWAPClampOrScarcity() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        // No VWAP data, large ETH balance, small supply
        strategy.setVWAP(0, 0);

        uint256 floorEthBalance = 100_000 ether; // Very large
        uint256 pulledHarb = 100 ether; // Small supply
        uint256 discoveryAmount = 50 ether;

        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);

        // With no VWAP: mirror = current. Floor uses max(scarcity, clamp).
        // The scarcity formula with small supply and large ETH may still push floor
        // significantly beyond the clamp minimum. Just verify floor is on correct side.
        int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
        int24 minSpacing = 200 + (34 * 50 * 200 / 100); // 3600
        assertTrue(centerTick >= CURRENT_TICK + minSpacing, "Floor should be positioned away from current tick to avoid anchor overlap");
    }

    function testFloorPositionOutstandingSupplyCalculation() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        uint256 initialSupply = 1_000_000 ether;
        uint256 pulledHarb = 50_000 ether;
        uint256 discoveryAmount = 30_000 ether;

        strategy.setOutstandingSupply(initialSupply);

        uint256 floorEthBalance = 80 ether;
        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        // The outstanding supply calculation should account for both pulled and discovery amounts
        // We can't directly observe this, but it affects the VWAP price calculation
        // This test ensures the function completes without reverting
        assertEq(strategy.getMintedPositionsCount(), 1, "Floor position should be created");
    }

    // ========================================
    // INTEGRATED POSITION SETTING TESTS
    // ========================================

    function testSetPositionsOrder() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        strategy.setPositions(CURRENT_TICK, params);

        // Should have created all three positions
        assertEq(strategy.getMintedPositionsCount(), 3, "Should create three positions");

        // Verify order: ANCHOR, DISCOVERY, FLOOR
        MockThreePositionStrategy.MintedPosition memory pos1 = strategy.getMintedPosition(0);
        MockThreePositionStrategy.MintedPosition memory pos2 = strategy.getMintedPosition(1);
        MockThreePositionStrategy.MintedPosition memory pos3 = strategy.getMintedPosition(2);

        assertEq(uint256(pos1.stage), uint256(ThreePositionStrategy.Stage.ANCHOR), "First should be anchor");
        assertEq(uint256(pos2.stage), uint256(ThreePositionStrategy.Stage.DISCOVERY), "Second should be discovery");
        assertEq(uint256(pos3.stage), uint256(ThreePositionStrategy.Stage.FLOOR), "Third should be floor");
    }

    function testSetPositionsEthAllocation() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.anchorShare = 2 * 10 ** 17; // 20%

        uint256 totalEth = 100 ether;
        strategy.setEthBalance(totalEth);

        strategy.setPositions(CURRENT_TICK, params);

        // Floor should get majority of ETH (75-95% according to contract logic)
        // Anchor should get remainder
        // This is validated by the positions being created successfully
        assertEq(strategy.getMintedPositionsCount(), 3, "All positions should be created with proper ETH allocation");
    }

    function testSetPositionsAsymmetricProfile() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        strategy.setPositions(CURRENT_TICK, params);

        MockThreePositionStrategy.MintedPosition memory anchor = strategy.getMintedPosition(0);
        MockThreePositionStrategy.MintedPosition memory discovery = strategy.getMintedPosition(1);
        MockThreePositionStrategy.MintedPosition memory floor = strategy.getMintedPosition(2);

        // Verify asymmetric slippage profile
        // Anchor should have smaller range (shallow liquidity, high slippage)
        int24 anchorRange = anchor.tickUpper - anchor.tickLower;
        int24 discoveryRange = discovery.tickUpper - discovery.tickLower;
        int24 floorRange = floor.tickUpper - floor.tickLower;

        // Discovery and floor should generally have wider ranges than anchor
        assertGt(discoveryRange, anchorRange / 2, "Discovery should have meaningful range");
        assertGt(floorRange, 0, "Floor should have positive range");

        // All positions should be positioned relative to current tick
        assertGt(anchor.liquidity, 0, "Anchor should have liquidity");
        assertGt(discovery.liquidity, 0, "Discovery should have liquidity");
        assertGt(floor.liquidity, 0, "Floor should have liquidity");
    }

    // ========================================
    // POSITION BOUNDARY TESTS
    // ========================================

    function testPositionBoundaries() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();

        strategy.setPositions(CURRENT_TICK, params);

        MockThreePositionStrategy.MintedPosition memory anchor = strategy.getMintedPosition(0);
        MockThreePositionStrategy.MintedPosition memory discovery = strategy.getMintedPosition(1);
        MockThreePositionStrategy.MintedPosition memory floor = strategy.getMintedPosition(2);

        // Verify positions don't overlap inappropriately
        // This is important for the valley liquidity strategy

        // All ticks should be properly aligned to tick spacing
        assertEq(anchor.tickLower % 200, 0, "Anchor lower tick should be aligned");
        assertEq(anchor.tickUpper % 200, 0, "Anchor upper tick should be aligned");
        assertEq(discovery.tickLower % 200, 0, "Discovery lower tick should be aligned");
        assertEq(discovery.tickUpper % 200, 0, "Discovery upper tick should be aligned");
        assertEq(floor.tickLower % 200, 0, "Floor lower tick should be aligned");
        assertEq(floor.tickUpper % 200, 0, "Floor upper tick should be aligned");
    }

    // ========================================
    // PARAMETER VALIDATION TESTS
    // ========================================

    function testParameterBounding() public {
        // Test that large but realistic parameters are handled gracefully
        ThreePositionStrategy.PositionParams memory extremeParams = ThreePositionStrategy.PositionParams({
            capitalInefficiency: 10 ** 18, // 100% (maximum reasonable value)
            anchorShare: 10 ** 18, // 100% (maximum reasonable value)
            anchorWidth: 1000, // Very wide anchor
            discoveryDepth: 10 ** 18 // 100% (maximum reasonable value)
         });

        // Should not revert even with extreme parameters
        strategy.setPositions(CURRENT_TICK, extremeParams);
        assertEq(strategy.getMintedPositionsCount(), 3, "Should handle extreme parameters gracefully");
    }

    // ========================================
    // ANCHOR WIDTH BOUNDS TESTS (#817)
    // ========================================

    function testAnchorWidthAtMaxBoundarySucceeds() public {
        // MAX_ANCHOR_WIDTH = 1233: 34 * 1233 * 200 = 8,384,400 fits within int24 max (8,388,607)
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.anchorWidth = 1233;

        strategy.setAnchorPosition(CURRENT_TICK, 20 ether, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertTrue(pos.tickLower < pos.tickUpper, "tickLower must be less than tickUpper");
        assertTrue(pos.tickLower >= -887272, "tickLower must be >= MIN_TICK");
        assertTrue(pos.tickUpper <= 887272, "tickUpper must be <= MAX_TICK");
        assertGt(pos.liquidity, 0, "Anchor should have positive liquidity");
    }

    function testAnchorWidthAboveMaxOverflowsAtStrategyLayer() public {
        // Calling the strategy directly with anchorWidth=1234 panics at the int24 multiplication
        // (34 * 1234 * 200 = 8,391,200 > int24 max 8,388,607). This demonstrates why
        // LiquidityManager clamps anchorWidth to MAX_ANCHOR_WIDTH before calling _setPositions.
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.anchorWidth = 1234;

        vm.expectRevert();
        strategy.setAnchorPosition(CURRENT_TICK, 20 ether, params);
    }

    // ========================================
    // VWAP INT128 OVERFLOW GUARD (#622)
    // ========================================

    /// @notice VWAP values that would overflow int128 in _tickAtPriceRatio are handled
    ///         gracefully — floor is placed using scarcity/clamp signals instead of reverting (#622).
    function testFloorPositionLargeVWAPNoOverflow() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.capitalInefficiency = 0; // adjustedVWAP = 7*rawVwap/10

        // Set VWAP so that getAdjustedVWAP(0) >> 32 exceeds int128.max (2^127-1).
        // rawVwap = 2^160 → adjustedVwap ≈ 7*2^160/10 ≈ 2^159.1 → shifted ≈ 2^127.1 > int128.max
        uint256 hugeVwap = uint256(1) << 160;
        strategy.setVWAP(hugeVwap, 1);

        uint256 floorEthBalance = 80 ether;
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        // Must not revert — mirror tick is skipped, floor uses scarcity/clamp fallback
        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.FLOOR), "Should be floor position");
        assertTrue(pos.liquidity > 0, "Floor should have liquidity");
    }

    /// @notice VWAP at uint256.max >> 32 boundary — must not revert.
    function testFloorPositionMaxVWAPNoOverflow() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.capitalInefficiency = 0;

        // Use a very large VWAP near uint256 limits
        // getAdjustedVWAP(0) = 7 * rawVwap / 10 — stays within uint256
        uint256 maxSafeVwap = type(uint256).max / 7; // avoids overflow in 7*rawVwap
        strategy.setVWAP(maxSafeVwap, 1);

        uint256 floorEthBalance = 80 ether;
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        // Must not revert — int128 guard skips mirror tick
        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertTrue(pos.liquidity > 0, "Floor should have liquidity even with extreme VWAP");
    }

    /// @notice Normal VWAP values (below int128 threshold) still compute mirror tick correctly.
    function testFloorPositionNormalVWAPStillUsesMirror() public {
        ThreePositionStrategy.PositionParams memory params = getDefaultParams();
        params.capitalInefficiency = 0;

        // Set a normal VWAP (1.0 in X96 format) — well below int128.max after >> 32
        uint256 normalVwap = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96
        strategy.setVWAP(normalVwap, 1000 ether);

        uint256 floorEthBalance = 80 ether;
        uint256 pulledHarb = 1000 ether;
        uint256 discoveryAmount = 500 ether;

        strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);

        MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
        assertEq(uint256(pos.stage), uint256(ThreePositionStrategy.Stage.FLOOR), "Should be floor position");
        assertTrue(pos.liquidity > 0, "Floor should have liquidity");
    }
}
