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

import { IWETH9 } from "../../src/interfaces/IWETH9.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/IERC20.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import { IUniswapV3MintCallback } from "@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3MintCallback.sol";

/// @title BackgroundLP
/// @notice Simulates competing liquidity providers with stacked positions
///         approximating a Gaussian curve centered on current tick.
///         Rebalances on demand to track the market.
contract BackgroundLP is IUniswapV3MintCallback {
    IUniswapV3Pool public pool;
    IWETH9 public weth;
    IERC20 public kraiken;
    bool public token0isWeth;
    int24 public tickSpacing;

    // Track active positions for cleanup
    struct Position {
        int24 tickLower;
        int24 tickUpper;
        uint128 liquidity;
    }

    Position[6] public positions;
    uint256 public numPositions;

    // Gaussian approximation: 5 stacked positions with increasing width
    // Widths in multiples of tickSpacing
    // Layer 0: ±10 spacings (narrowest, highest concentration)
    // Layer 1: ±20 spacings
    // Layer 2: ±40 spacings
    // Layer 3: ±80 spacings
    // Layer 4: ±160 spacings (widest, lowest concentration)
    uint24[5] public HALF_WIDTHS = [10, 20, 40, 80, 160];

    constructor(IUniswapV3Pool _pool, IWETH9 _weth, IERC20 _kraiken, bool _token0isWeth) {
        pool = _pool;
        weth = _weth;
        kraiken = _kraiken;
        token0isWeth = _token0isWeth;
        tickSpacing = pool.tickSpacing();
    }

    /// @notice Deploy stacked positions centered on current tick
    /// @param ethPerLayer ETH to allocate per layer (total = 5 × ethPerLayer)
    function rebalance(uint256 ethPerLayer) external {
        // 1. Remove old positions
        _burnAll();

        // 2. Get current tick
        (, int24 currentTick,,,,,) = pool.slot0();
        int24 centerTick = (currentTick / tickSpacing) * tickSpacing;

        // 3. Deploy 5 stacked layers
        for (uint256 i = 0; i < 5; i++) {
            int24 halfWidth = int24(uint24(HALF_WIDTHS[i])) * tickSpacing;
            int24 tickLower = centerTick - halfWidth;
            int24 tickUpper = centerTick + halfWidth;

            // Clamp to valid range
            if (tickLower < -887_200) tickLower = -887_200;
            if (tickUpper > 887_200) tickUpper = 887_200;

            // Calculate liquidity from ETH amount
            // Use half the ethPerLayer since we need both tokens for in-range positions
            uint128 liquidity = _getLiquidityForEth(tickLower, tickUpper, ethPerLayer);
            if (liquidity == 0) continue;

            // Mint position
            pool.mint(address(this), tickLower, tickUpper, liquidity, "");

            positions[numPositions] = Position(tickLower, tickUpper, liquidity);
            numPositions++;
        }
    }

    /// @notice Burn all active positions
    function _burnAll() internal {
        for (uint256 i = 0; i < numPositions; i++) {
            Position memory pos = positions[i];
            if (pos.liquidity > 0) {
                pool.burn(pos.tickLower, pos.tickUpper, pos.liquidity);
                // Collect tokens back
                pool.collect(address(this), pos.tickLower, pos.tickUpper, type(uint128).max, type(uint128).max);
            }
        }
        numPositions = 0;
    }

    /// @notice Calculate liquidity for a given ETH amount and tick range
    function _getLiquidityForEth(int24 tickLower, int24 tickUpper, uint256 ethAmount) internal view returns (uint128) {
        uint160 sqrtLower = TickMath.getSqrtRatioAtTick(tickLower);
        uint160 sqrtUpper = TickMath.getSqrtRatioAtTick(tickUpper);
        (, int24 currentTick,,,,,) = pool.slot0();
        uint160 sqrtCurrent = TickMath.getSqrtRatioAtTick(currentTick);

        if (token0isWeth) {
            // WETH is token0 — for in-range, need amount0
            if (sqrtCurrent <= sqrtLower) {
                // All token0 (WETH)
                return LiquidityAmounts.getLiquidityForAmount0(sqrtLower, sqrtUpper, ethAmount);
            } else if (sqrtCurrent < sqrtUpper) {
                // In range — use amount0 portion
                return LiquidityAmounts.getLiquidityForAmount0(sqrtCurrent, sqrtUpper, ethAmount / 2);
            } else {
                return 0; // All token1, no ETH needed
            }
        } else {
            // WETH is token1 — for in-range, need amount1
            if (sqrtCurrent >= sqrtUpper) {
                // All token1 (WETH)
                return LiquidityAmounts.getLiquidityForAmount1(sqrtLower, sqrtUpper, ethAmount);
            } else if (sqrtCurrent > sqrtLower) {
                // In range — use amount1 portion
                return LiquidityAmounts.getLiquidityForAmount1(sqrtLower, sqrtCurrent, ethAmount / 2);
            } else {
                return 0; // All token0, no ETH needed
            }
        }
    }

    /// @notice Uniswap V3 mint callback — provide tokens
    function uniswapV3MintCallback(uint256 amount0Owed, uint256 amount1Owed, bytes calldata) external override {
        require(msg.sender == address(pool), "not pool");

        if (amount0Owed > 0) {
            IERC20(token0isWeth ? address(weth) : address(kraiken)).transfer(msg.sender, amount0Owed);
        }
        if (amount1Owed > 0) {
            IERC20(token0isWeth ? address(kraiken) : address(weth)).transfer(msg.sender, amount1Owed);
        }
    }

    /// @notice Allow receiving ETH
    receive() external payable { }
}

// Import 0.8-compatible math from aperture's uni-v3-lib

import { LiquidityAmounts } from "@aperture/uni-v3-lib/LiquidityAmounts.sol";
import { TickMath } from "@aperture/uni-v3-lib/TickMath.sol";
