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

import { Kraiken } from "../../src/Kraiken.sol";
import { ThreePositionStrategy } from "../../src/abstracts/ThreePositionStrategy.sol";
import "../../src/interfaces/IWETH9.sol";
import { LiquidityBoundaryHelper } from "./LiquidityBoundaryHelper.sol";
import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
import { SqrtPriceMath } from "@aperture/uni-v3-lib/SqrtPriceMath.sol";
import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol";
import "forge-std/Test.sol";

/**
 * @title UniSwapHelper
 * @dev Helper contract for Uniswap V3 testing, providing reusable swap logic.
 */
abstract contract UniSwapHelper is Test {
    address account = makeAddr("alice");
    IUniswapV3Pool public pool;
    IWETH9 public weth;
    Kraiken public harberg;
    bool public token0isWeth;

    /**
     * @dev Performs a swap in the Uniswap V3 pool.
     * @param amount The amount to swap.
     * @param isBuy True if buying WETH, false if selling.
     */
    function performSwap(uint256 amount, bool isBuy) public {
        uint160 limit;
        // Determine the swap direction
        bool zeroForOne = isBuy ? token0isWeth : !token0isWeth;

        if (isBuy) {
            vm.prank(account);
            weth.transfer(address(this), amount);
        } else {
            vm.prank(account);
            harberg.approve(address(this), amount);
        }

        // Set the sqrtPriceLimitX96 based on the swap direction
        // Get current price to set appropriate limits
        (uint160 currentSqrtPrice,,,,,,) = pool.slot0();

        if (zeroForOne) {
            // Swapping token0 for token1 - price goes down
            // sqrtPriceLimitX96 must be less than current price but greater than MIN_SQRT_RATIO
            uint160 minAllowedLimit = TickMath.MIN_SQRT_RATIO + 1;
            // Safety check: ensure we have enough room to set a valid limit
            if (currentSqrtPrice <= minAllowedLimit + 1) {
                // Emergency fallback: current price is at or very close to minimum
                // We can't safely set a limit, so use the minimum possible
                limit = minAllowedLimit;
            } else {
                // Use aggressive limit close to MIN_SQRT_RATIO to allow full price movement
                limit = minAllowedLimit;
            }
        } else {
            // Swapping token1 for token0 - price goes up
            // sqrtPriceLimitX96 must be greater than current price but less than MAX_SQRT_RATIO
            uint160 maxAllowedLimit = TickMath.MAX_SQRT_RATIO - 1;
            // Safety check: ensure we have enough room to set a valid limit
            if (currentSqrtPrice >= maxAllowedLimit - 1) {
                // Emergency fallback: current price is at or very close to maximum
                // We can't safely set a limit, so use the maximum possible
                limit = maxAllowedLimit;
            } else {
                // Use aggressive limit close to MAX_SQRT_RATIO to allow full price movement
                limit = maxAllowedLimit;
            }
        }

        pool.swap(account, zeroForOne, int256(amount), limit, abi.encode(account, int256(amount), isBuy));
    }

    /**
     * @notice Performs a swap with aggressive price limits for extreme price normalization
     * @param amount The amount to swap
     * @param isBuy True if buying HARB, false if selling HARB
     */
    function performSwapWithAggressiveLimits(uint256 amount, bool isBuy) internal {
        uint160 limit;
        // Determine the swap direction
        bool zeroForOne = isBuy ? token0isWeth : !token0isWeth;

        if (isBuy) {
            vm.prank(account);
            weth.transfer(address(this), amount);
        } else {
            vm.prank(account);
            harberg.approve(address(this), amount);
        }

        // Set aggressive price limits that allow price to move to liquidity ranges
        if (zeroForOne) {
            // Swapping token0 for token1 - price goes down
            // Use very aggressive limit close to MIN_SQRT_RATIO
            limit = TickMath.MIN_SQRT_RATIO + 1;
        } else {
            // Swapping token1 for token0 - price goes up
            // Use very aggressive limit close to MAX_SQRT_RATIO
            limit = TickMath.MAX_SQRT_RATIO - 1;
        }

        pool.swap(account, zeroForOne, int256(amount), limit, abi.encode(account, int256(amount), isBuy));
    }

    /**
     * @dev The Uniswap V3 swap callback.
     */
    function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) external {
        // Handle the case where no swap occurred (both deltas are 0)
        if (amount0Delta == 0 && amount1Delta == 0) {
            return;
        }

        require(amount0Delta > 0 || amount1Delta > 0);

        (address seller,, bool isBuy) = abi.decode(_data, (address, uint256, bool));

        (, uint256 amountToPay) = amount0Delta > 0 ? (!token0isWeth, uint256(amount0Delta)) : (token0isWeth, uint256(amount1Delta));
        if (isBuy) {
            weth.transfer(msg.sender, amountToPay);
        } else {
            require(harberg.transferFrom(seller, msg.sender, amountToPay), "Transfer failed");
        }
    }

    /// @notice Callback function that Uniswap V3 calls for liquidity actions requiring minting or burning of tokens.
    /// @param amount0Owed The amount of token0 owed for the liquidity provision.
    /// @param amount1Owed The amount of token1 owed for the liquidity provision.
    /// @dev This function mints Kraiken tokens as needed and handles WETH deposits for ETH conversions during liquidity interactions.
    function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external {
        // CallbackValidation.verifyCallback(factory, poolKey);
        // take care of harb
        uint256 harbPulled = token0isWeth ? amount1Owed : amount0Owed;
        if (harbPulled > 0) {
            harberg.mint(harbPulled);
            harberg.transfer(msg.sender, harbPulled);
        }

        // pack ETH
        uint256 ethOwed = token0isWeth ? amount0Owed : amount1Owed;
        if (weth.balanceOf(address(this)) < ethOwed) {
            weth.deposit{ value: address(this).balance }();
        }
        if (ethOwed > 0) {
            weth.transfer(msg.sender, amount1Owed);
        }
    }

    // ========================================
    // EXTREME PRICE HANDLING
    // ========================================

    // Safety margin to prevent tick boundary violations (conservative approach)
    int24 constant TICK_BOUNDARY_SAFETY_MARGIN = 15_000;

    // Price normalization constants
    uint256 constant NORMALIZATION_HARB_PERCENTAGE = 100; // 1% of HARB balance
    uint256 constant NORMALIZATION_ETH_AMOUNT = 0.01 ether; // Fixed ETH amount for normalization
    uint256 constant MAX_NORMALIZATION_ATTEMPTS = 3; // Prevent infinite loops
    uint256 constant PRICE_LIMIT_BUFFER = 1000; // Buffer from sqrt price limits

    /**
     * @notice Handles extreme price conditions by executing normalizing trades
     * @dev This function should be called before any recenter operation to ensure
     *      the price is within safe boundaries for liquidity position creation
     */
    function handleExtremePrice() internal {
        uint256 attempts = 0;

        while (attempts < MAX_NORMALIZATION_ATTEMPTS) {
            (, int24 currentTick,,,,,) = pool.slot0();

            if (currentTick >= TickMath.MAX_TICK - TICK_BOUNDARY_SAFETY_MARGIN) {
                _executeNormalizingTrade(true); // Move price down
                attempts++;
            } else if (currentTick <= TickMath.MIN_TICK + TICK_BOUNDARY_SAFETY_MARGIN) {
                _executeNormalizingTrade(false); // Move price up
                attempts++;
            } else {
                // Price is now safe, exit loop
                break;
            }
        }
    }

    /**
     * @notice Executes a small trade to move price away from tick boundaries
     * @param moveDown True to move price down (sell HARB), false to move price up (buy HARB)
     */
    function _executeNormalizingTrade(bool moveDown) internal {
        if (moveDown) {
            // Need to move price DOWN (reduce HARB price)
            // This means: sell HARB for ETH (increase HARB supply in pool)
            uint256 harbBalance = harberg.balanceOf(account);
            if (harbBalance > 0) {
                // Use 1% of account's HARB balance (conservative approach like original)
                uint256 harbToSell = harbBalance / NORMALIZATION_HARB_PERCENTAGE;
                if (harbToSell == 0) harbToSell = 1;

                vm.prank(account);
                harberg.transfer(address(this), harbToSell);
                harberg.approve(address(pool), harbToSell);

                // Sell HARB for ETH with aggressive price limits for normalization
                performSwapWithAggressiveLimits(harbToSell, false);
            }
        } else {
            // Need to move price UP (increase HARB price)
            // This means: buy HARB with ETH (reduce HARB supply in pool)
            uint256 ethBalance = weth.balanceOf(account);
            if (ethBalance > 0) {
                // Use small amount for normalization (like original)
                uint256 ethToBuy = NORMALIZATION_ETH_AMOUNT;
                if (ethToBuy > ethBalance) ethToBuy = ethBalance;

                // Buy HARB with ETH with aggressive price limits for normalization
                performSwapWithAggressiveLimits(ethToBuy, true);
            }
        }
    }

    // ========================================
    // LIQUIDITY-AWARE TRADE SIZE CALCULATION
    // ========================================

    /**
     * @notice Calculates the maximum ETH amount that can be traded (buy HARB) without exceeding position liquidity limits
     * @dev When currentTick is in anchor range, calculates trade size to make anchor and discovery positions "full" of ETH
     * @return maxEthAmount Maximum ETH that can be safely traded, 0 if no positions exist or already at limit
     */
    function buyLimitToLiquidityBoundary() internal view returns (uint256 maxEthAmount) {
        // Get LiquidityManager reference from test context
        // This assumes the test has a 'lm' variable for the LiquidityManager
        try this.getLiquidityManager() returns (ThreePositionStrategy liquidityManager) {
            return LiquidityBoundaryHelper.calculateBuyLimit(pool, liquidityManager, token0isWeth);
        } catch {
            return 0; // Safe fallback if LiquidityManager access fails
        }
    }

    /**
     * @notice Calculates the maximum HARB amount that can be traded (sell HARB) without exceeding position liquidity limits
     * @dev When currentTick is in anchor range, calculates trade size to make anchor and floor positions "full" of HARB
     * @return maxHarbAmount Maximum HARB that can be safely traded, 0 if no positions exist or already at limit
     */
    function sellLimitToLiquidityBoundary() internal view returns (uint256 maxHarbAmount) {
        try this.getLiquidityManager() returns (ThreePositionStrategy liquidityManager) {
            return LiquidityBoundaryHelper.calculateSellLimit(pool, liquidityManager, token0isWeth);
        } catch {
            return 0; // Safe fallback if LiquidityManager access fails
        }
    }

    /**
     * @notice Helper function to get LiquidityManager reference from test context
     * @dev This function should be overridden in the test contract to return the actual LiquidityManager instance
     * @return liquidityManager The LiquidityManager contract instance
     */
    function getLiquidityManager() external view virtual returns (ThreePositionStrategy liquidityManager) {
        revert("getLiquidityManager must be implemented in test contract");
    }

    /**
     * @notice Raw buy operation without liquidity limit checking
     * @param amountEth Amount of ETH to spend buying HARB
     */
    function buyRaw(uint256 amountEth) internal {
        performSwap(amountEth, true);
        // Note: No checkLiquidity call - this is the "raw" version
    }

    /**
     * @notice Raw sell operation without liquidity limit checking
     * @param amountHarb Amount of HARB to sell for ETH
     */
    function sellRaw(uint256 amountHarb) internal {
        performSwap(amountHarb, false);
        // Note: No checkLiquidity call - this is the "raw" version
    }
}
