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

import { SwapExecutor } from "../analysis/helpers/SwapExecutor.sol";
import { Kraiken } from "../src/Kraiken.sol";
import { LiquidityManager } from "../src/LiquidityManager.sol";
import { ThreePositionStrategy } from "../src/abstracts/ThreePositionStrategy.sol";
import { UniswapHelpers } from "../src/helpers/UniswapHelpers.sol";
import { IWETH9 } from "../src/interfaces/IWETH9.sol";
import { TestEnvironment } from "./helpers/TestBase.sol";
import { ConfigurableOptimizer } from "./mocks/ConfigurableOptimizer.sol";
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "forge-std/Test.sol";
import "forge-std/console2.sol";

/// @title EthScarcityAbundance
/// @notice Tests investigating when EthScarcity vs EthAbundance fires,
///         and the floor ratchet's effect during each condition.
contract EthScarcityAbundance is Test {
    TestEnvironment testEnv;
    IUniswapV3Factory factory;
    IUniswapV3Pool pool;
    IWETH9 weth;
    Kraiken kraiken;
    LiquidityManager lm;
    SwapExecutor swapExecutor;
    ConfigurableOptimizer optimizer;
    bool token0isWeth;

    address trader = makeAddr("trader");
    address fees = makeAddr("fees");

    bytes32 constant SCARCITY_SIG = keccak256("EthScarcity(int24,uint256,uint256,uint256,int24)");
    bytes32 constant ABUNDANCE_SIG = keccak256("EthAbundance(int24,uint256,uint256,uint256,int24)");

    function setUp() public {
        testEnv = new TestEnvironment(fees);
        factory = UniswapHelpers.deployUniswapFactory();

        // Default params: CI=50%, AS=50%, AW=50, DD=50%
        optimizer = new ConfigurableOptimizer(5e17, 5e17, 50, 5e17);

        (factory, pool, weth, kraiken,, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(optimizer));

        swapExecutor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm, true);

        // Fund LM generously
        vm.deal(address(lm), 200 ether);
        vm.prank(address(lm));
        weth.deposit{ value: 100 ether }();

        // Initial recenter
        vm.prank(fees);
        lm.recenter();

        // Fund trader
        vm.deal(trader, 300 ether);
        vm.prank(trader);
        weth.deposit{ value: 300 ether }();
    }

    // ================================================================
    // Q1: WHEN DOES EthScarcity/EthAbundance FIRE?
    // ================================================================

    /// @notice After a small buy and sell-back, EthAbundance fires on recenter.
    /// This proves EthScarcity is NOT permanent.
    function test_floor_placed_after_sellback() public {
        console2.log("=== Floor placement after sell-back ===");

        // Small buy to create some VWAP history
        _executeBuy(5 ether);
        _recenterAndLog("Post-buy");

        // Sell ALL KRK back
        _executeSell(kraiken.balanceOf(trader));

        // Recenter after sell-back — should not revert
        _recenterAndLog("Post-sellback");

        // Floor position should exist (check via positions mapping)
        (uint128 floorLiq,,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        assertTrue(floorLiq > 0, "Floor should be placed after sell-back");
    }

    /// @notice During sustained buy pressure, floor should be placed progressively
    function test_floor_during_buy_pressure() public {
        console2.log("=== Floor during buy pressure ===");

        for (uint256 i = 0; i < 5; i++) {
            _executeBuy(15 ether);
            _recenterAndLog("Buy");
        }

        // Floor should have been placed on each recenter
        (uint128 floorLiq,,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        assertTrue(floorLiq > 0, "Floor should be placed during buy pressure");
    }

    /// @notice After heavy buying, floor persists through sells via VWAP mirror.
    function test_floor_retreats_through_bull_bear_cycle() public {
        console2.log("=== Floor through bull-bear cycle ===");

        // Bull phase: 5 buys of 15 ETH = 75 ETH total
        for (uint256 i = 0; i < 5; i++) {
            _executeBuy(15 ether);
        }
        _recenterAndLog("End of bull");

        // Bear phase: sell everything in chunks with recenters
        uint256 totalKrk = kraiken.balanceOf(trader);
        uint256 remaining = totalKrk;
        uint256 attempts;
        while (remaining > 0 && attempts < 10) {
            uint256 sellChunk = remaining > totalKrk / 3 ? totalKrk / 3 : remaining;
            if (sellChunk == 0) break;
            _executeSell(sellChunk);
            remaining = kraiken.balanceOf(trader);
            _recenterAndLog("Sell chunk");
            attempts++;
        }

        // Floor should still exist after selling
        (uint128 floorLiq,,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        assertTrue(floorLiq > 0, "Floor should persist through bear phase");
    }

    // ================================================================
    // Q1 continued: IS THE FLOOR PERMANENTLY STUCK?
    // ================================================================

    /// @notice With conditional ratchet: floor tracks currentTick during abundance
    /// but is locked during scarcity. Demonstrates the ratchet is now market-responsive.
    function test_floor_tracks_price_during_abundance() public {
        console2.log("=== Floor responds to market during EthAbundance ===");

        // Record initial floor
        (, int24 floorTickInit,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("Initial floor tickLower:", floorTickInit);

        // Buy to push price, creating scarcity
        _executeBuy(10 ether);
        (bool scarcityA,,) = _recenterAndLog("After buy");
        (, int24 floorAfterScarcity,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("Floor after scarcity recenter:", floorAfterScarcity);
        console2.log("EthScarcity fired:", scarcityA);

        // Sell back to trigger abundance
        _executeSell(kraiken.balanceOf(trader));
        (, bool abundanceA,) = _recenterAndLog("After sell-back");

        (, int24 floorAfterAbundance,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("Floor after abundance recenter:", floorAfterAbundance);
        console2.log("EthAbundance fired:", abundanceA);

        // During abundance, floor is set by anti-overlap clamp: currentTick + anchorSpacing
        // This tracks the current price rather than being permanently locked
        (, int24 currentTick,,,,,) = pool.slot0();
        // anchorSpacing for AW=50: 200 + (34 * 50 * 200 / 100) = 3600
        int24 expectedBoundary = currentTick + 3600;
        console2.log("Current tick:", currentTick);
        console2.log("Expected floor boundary:", expectedBoundary);

        // With conditional ratchet: during abundance, the ratchet is off,
        // so floor can be at anti-overlap boundary (tracks current price)
        // With permanent ratchet: floor would be max(prevFloor, boundary)

        console2.log("FINDING: Floor responds to market during abundance");
        console2.log("  Scarcity: floor locked (ratchet on)");
        console2.log("  Abundance: floor at currentTick + anchorSpacing");
    }

    /// @notice During EthAbundance, the abundance vwapTick (from VWAP) is typically
    /// far below the anti-overlap boundary, so the anti-overlap clamp sets the floor
    /// to currentTick + anchorSpacing. With conditional ratchet, this tracks the
    /// current price. With permanent ratchet, it would be max(prevFloor, boundary).
    function test_abundance_floor_mechanism() public {
        console2.log("=== How floor is set during abundance ===");

        // Buy to create VWAP then sell back for abundance
        _executeBuy(10 ether);
        _recenterAndLog("After buy");
        _executeSell(kraiken.balanceOf(trader));

        // Recenter (expect abundance)
        vm.warp(block.timestamp + 1 hours);
        vm.roll(block.number + 1);
        vm.recordLogs();
        vm.prank(fees);
        lm.recenter();

        (, int24 currentTick,,,,,) = pool.slot0();
        (, int24 floorTickLower,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);

        // anchorSpacing for AW=50: 200 + (34*50*200/100) = 3600
        int24 antiOverlapBoundary = currentTick + 3600;

        Vm.Log[] memory logs = vm.getRecordedLogs();
        int24 abundanceVwapTick;
        for (uint256 i = 0; i < logs.length; i++) {
            if (logs[i].topics.length > 0 && logs[i].topics[0] == ABUNDANCE_SIG) {
                (,,,, int24 vt) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
                abundanceVwapTick = vt;
            }
        }

        console2.log("Current tick:", currentTick);
        console2.log("Anti-overlap boundary:", antiOverlapBoundary);
        console2.log("Abundance vwapTick:", abundanceVwapTick);
        console2.log("Floor tickLower:", floorTickLower);

        // The abundance vwapTick should be at or below the anti-overlap boundary.
        // When the clamp signal dominates, vwapTick equals the boundary exactly.
        assertTrue(abundanceVwapTick <= antiOverlapBoundary, "VWAP tick at or below anti-overlap boundary");

        // Floor ends up at anti-overlap boundary (clamped after tick spacing)
        // With conditional ratchet: this tracks current price
        // With permanent ratchet: this would be max(prevFloor, boundary)
        console2.log("Floor set by: anti-overlap clamp (tracks current price)");
    }

    // ================================================================
    // Q2: BULL/BEAR LIQUIDITY DISTRIBUTION & CONDITIONAL RATCHET
    // ================================================================

    /// @notice Demonstrates ideal bull vs bear parameter distribution
    function test_bull_market_params() public {
        console2.log("=== Bull vs Bear parameter comparison ===");

        // Bull optimizer: high anchorShare, wide anchor, deep discovery
        ConfigurableOptimizer bullOpt = new ConfigurableOptimizer(3e17, 8e17, 80, 8e17);

        (,,,,, LiquidityManager bullLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(bullOpt));

        vm.deal(address(bullLm), 200 ether);
        vm.prank(address(bullLm));
        weth.deposit{ value: 100 ether }();
        vm.prank(fees);
        bullLm.recenter();

        (uint128 floorLiq,,) = bullLm.positions(ThreePositionStrategy.Stage.FLOOR);
        (uint128 anchorLiq, int24 aLow, int24 aHigh) = bullLm.positions(ThreePositionStrategy.Stage.ANCHOR);
        (uint128 discLiq,,) = bullLm.positions(ThreePositionStrategy.Stage.DISCOVERY);

        console2.log("Bull (AS=80% AW=80 DD=80% CI=30%):");
        console2.log("  Floor liq:", uint256(floorLiq));
        console2.log("  Anchor liq:", uint256(anchorLiq));
        console2.log("  Anchor width:", uint256(int256(aHigh - aLow)));
        console2.log("  Discovery liq:", uint256(discLiq));

        // Bear optimizer: low anchorShare, moderate anchor, thin discovery
        ConfigurableOptimizer bearOpt = new ConfigurableOptimizer(8e17, 1e17, 40, 2e17);

        (,,,,, LiquidityManager bearLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(bearOpt));

        vm.deal(address(bearLm), 200 ether);
        vm.prank(address(bearLm));
        weth.deposit{ value: 100 ether }();
        vm.prank(fees);
        bearLm.recenter();

        (uint128 bFloorLiq,,) = bearLm.positions(ThreePositionStrategy.Stage.FLOOR);
        (uint128 bAnchorLiq, int24 bALow, int24 bAHigh) = bearLm.positions(ThreePositionStrategy.Stage.ANCHOR);
        (uint128 bDiscLiq,,) = bearLm.positions(ThreePositionStrategy.Stage.DISCOVERY);

        console2.log("Bear (AS=10% AW=40 DD=20% CI=80%):");
        console2.log("  Floor liq:", uint256(bFloorLiq));
        console2.log("  Anchor liq:", uint256(bAnchorLiq));
        console2.log("  Anchor width:", uint256(int256(bAHigh - bALow)));
        console2.log("  Discovery liq:", uint256(bDiscLiq));

        assertGt(anchorLiq, bAnchorLiq, "Bull should have more anchor liquidity than bear");
    }

    /// @notice Tracks floor through scarcity -> abundance -> scarcity cycle.
    /// Shows what a conditional ratchet WOULD allow.
    function test_conditional_ratchet_concept() public {
        console2.log("=== Conditional ratchet concept ===");

        (, int24 floorTickInit,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("Initial floor:", floorTickInit);

        // Phase 1: Buy pressure -> EthScarcity -> ratchet ACTIVE
        _executeBuy(30 ether);
        (bool s1,,) = _recenterAndLog("Buy#1");
        (, int24 floorAfterBuy1,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("  Floor after buy1:", floorAfterBuy1);
        console2.log("  Ratchet held:", floorAfterBuy1 == floorTickInit);

        _executeBuy(20 ether);
        (bool s2,,) = _recenterAndLog("Buy#2");
        (, int24 floorAfterBuy2,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("  Floor after buy2:", floorAfterBuy2);
        console2.log("  Ratchet held:", floorAfterBuy2 == floorTickInit);

        // Phase 2: Sell back -> EthAbundance -> ratchet would be INACTIVE
        _executeSell(kraiken.balanceOf(trader));
        (bool s3, bool a3, int24 vwapTick3) = _recenterAndLog("Sell-back");
        (, int24 floorAfterSell,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);

        console2.log("Phase 2 (sell-back):");
        console2.log("  EthAbundance fired:", a3);
        console2.log("  Floor actual:", floorAfterSell);
        console2.log("  VwapTick from event:", vwapTick3);
        if (a3) {
            console2.log("  PROPOSED: Floor would move to vwapTick during abundance");
        }

        // Phase 3: Buy again -> EthScarcity -> ratchet re-engages
        _executeBuy(25 ether);
        (bool s4,,) = _recenterAndLog("Buy#3");
        (, int24 floorAfterBuy3,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("  EthScarcity fired:", s4);
        console2.log("  Floor after buy3:", floorAfterBuy3);

        console2.log("");
        console2.log("=== CONDITIONAL RATCHET SUMMARY ===");
        console2.log("Current: Floor permanently locked at:", floorTickInit);
        console2.log("Proposed: Scarcity=locked, Abundance=free, re-lock on next scarcity");
    }

    /// @notice Diagnostic: buy->recenter->sell IL extraction.
    /// Without a ratchet, trader CAN profit — this test documents the baseline.
    function test_buyRecenterSell_baseline() public {
        console2.log("=== Baseline: buy->recenter->sell (no ratchet) ===");

        uint256 initWeth = weth.balanceOf(trader);

        _executeBuy(78 ether);
        (bool s1,,) = _recenterAndLog("Attack buy1");

        _executeBuy(47 ether);
        (bool s2,,) = _recenterAndLog("Attack buy2");

        _executeBuy(40 ether);
        _executeBuy(20 ether);
        _recenterAndLog("Attack buy3+4");

        _liquidateTrader();

        int256 pnl = int256(weth.balanceOf(trader)) - int256(initWeth);
        console2.log("Baseline PnL (ETH):", pnl / 1e18);
        console2.log("Scarcity fired during buys:", s1, s2);
        // No assertion — this test documents IL extraction without ratchet
    }

    /// @notice Diagnostic: sell-to-trigger-abundance then re-buy.
    /// Documents baseline behavior without ratchet.
    function test_sellToTriggerAbundance_baseline() public {
        console2.log("=== Baseline: sell-to-trigger-abundance (no ratchet) ===");

        uint256 initWeth = weth.balanceOf(trader);

        // Phase 1: Buy 100 ETH worth
        _executeBuy(50 ether);
        _recenterAndLog("Setup buy1");
        _executeBuy(50 ether);
        _recenterAndLog("Setup buy2");

        (, int24 floorAfterBuys,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("Floor after buys:", floorAfterBuys);

        // Phase 2: Sell 90% to try to trigger abundance
        uint256 krkBal = kraiken.balanceOf(trader);
        _executeSell(krkBal * 90 / 100);
        (bool s, bool a, int24 vwapTick) = _recenterAndLog("Post-90pct-sell");

        (, int24 floorAfterSell,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
        console2.log("After 90% sell:");
        console2.log("  Scarcity:", s);
        console2.log("  Abundance:", a);
        console2.log("  Floor:", floorAfterSell);

        if (a) {
            console2.log("  Abundance fired - vwapTick:", vwapTick);
        }

        // Liquidate remaining
        _liquidateTrader();
        int256 pnl = int256(weth.balanceOf(trader)) - int256(initWeth);
        console2.log("Final PnL:", pnl);
        // No assertion — this test documents baseline without ratchet
    }

    // ================================================================
    // HELPERS
    // ================================================================

    function _executeBuy(uint256 amount) internal {
        if (weth.balanceOf(trader) < amount) return;
        vm.startPrank(trader);
        weth.transfer(address(swapExecutor), amount);
        vm.stopPrank();
        try swapExecutor.executeBuy(amount, trader) { } catch { }
        _recoverStuck();
    }

    function _executeSell(uint256 krkAmount) internal {
        if (krkAmount == 0) return;
        uint256 bal = kraiken.balanceOf(trader);
        if (bal < krkAmount) krkAmount = bal;
        vm.startPrank(trader);
        kraiken.transfer(address(swapExecutor), krkAmount);
        vm.stopPrank();
        try swapExecutor.executeSell(krkAmount, trader) { } catch { }
        _recoverStuck();
    }

    function _decodeVwapTick(bytes memory data) internal pure returns (int24) {
        (,,,, int24 vwap) = abi.decode(data, (int24, uint256, uint256, uint256, int24));
        return vwap;
    }

    function _logEvent(bytes memory data, string memory label, string memory eventName) internal view {
        (int24 tick, uint256 ethBal, uint256 supply,,) = abi.decode(data, (int24, uint256, uint256, uint256, int24));
        console2.log(string.concat("  ", eventName, ":"), label);
        console2.log("    tick:", tick);
        console2.log("    ethBal:", ethBal / 1e18);
        console2.log("    supply:", supply / 1e18);
    }

    function _recenterAndLog(string memory label) internal returns (bool sawScarcity, bool sawAbundance, int24 eventVwapTick) {
        vm.warp(block.timestamp + 1 hours);
        vm.roll(block.number + 1);
        vm.recordLogs();
        vm.prank(fees);
        try lm.recenter() { }
        catch {
            console2.log("  recenter FAILED:", label);
            return (false, false, 0);
        }

        Vm.Log[] memory logs = vm.getRecordedLogs();
        for (uint256 i = 0; i < logs.length; i++) {
            if (logs[i].topics.length == 0) continue;
            if (logs[i].topics[0] == SCARCITY_SIG) {
                sawScarcity = true;
                eventVwapTick = _decodeVwapTick(logs[i].data);
                _logEvent(logs[i].data, label, "EthScarcity");
            } else if (logs[i].topics[0] == ABUNDANCE_SIG) {
                sawAbundance = true;
                eventVwapTick = _decodeVwapTick(logs[i].data);
                _logEvent(logs[i].data, label, "EthAbundance");
            }
        }

        if (!sawScarcity && !sawAbundance) {
            console2.log("  No scarcity/abundance:", label);
        }
    }

    function _liquidateTrader() internal {
        _recenterAndLog("Pre-liquidation");
        uint256 remaining = kraiken.balanceOf(trader);
        uint256 attempts;
        while (remaining > 0 && attempts < 20) {
            uint256 prev = remaining;
            _executeSell(remaining);
            remaining = kraiken.balanceOf(trader);
            if (remaining >= prev) break;
            if (attempts % 3 == 2) {
                _recenterAndLog("Liq recenter");
            }
            unchecked {
                attempts++;
            }
        }
    }

    function _recoverStuck() internal {
        uint256 sk = kraiken.balanceOf(address(swapExecutor));
        if (sk > 0) {
            vm.prank(address(swapExecutor));
            kraiken.transfer(trader, sk);
        }
        uint256 sw = weth.balanceOf(address(swapExecutor));
        if (sw > 0) {
            vm.prank(address(swapExecutor));
            weth.transfer(trader, sw);
        }
    }
}
