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

import { ConfigurableOptimizer } from "../test/mocks/ConfigurableOptimizer.sol";
import { FuzzingBase } from "./helpers/FuzzingBase.sol";
import "forge-std/console2.sol";

/// @title ParameterSweepFuzzing
/// @notice Runs multiple parameter combinations in a single forge execution.
/// @dev Key design: environment is deployed ONCE per combo and reused across runs
///      so that VWAP accumulates naturally across recenters.
///      Swaps are uncapped by default (no LiquidityBoundaryHelper limits).
///      LM ETH is tracked as direct balance + pool WETH (since LM is sole LP,
///      pool WETH represents LM's deployed position ETH).
contract ParameterSweepFuzzing is FuzzingBase {
    ConfigurableOptimizer optimizer;

    string summaryFile;

    uint256 cfgBuyBias;
    uint256 cfgTradesPerRun;
    uint256 cfgRunsPerCombo;
    uint256 cfgMinBuy;
    uint256 cfgMaxBuy;
    bool cfgUncapped;

    struct RunResult {
        uint256 lmEthInit;
        uint256 lmEthFinal;
        uint256 traderInitEth;
        uint256 traderFinalEth;
        uint256 numRecenters;
        uint256 numBuysFailed;
    }

    function run() public {
        cfgTradesPerRun = vm.envOr("TRADES_PER_RUN", uint256(30));
        cfgRunsPerCombo = vm.envOr("RUNS_PER_COMBO", uint256(5));
        cfgUncapped = vm.envOr("UNCAPPED_SWAPS", true);
        cfgMinBuy = vm.envOr("MIN_BUY_ETH", uint256(20));
        cfgMaxBuy = vm.envOr("MAX_BUY_ETH", uint256(80));
        string memory tag = vm.envOr("SWEEP_TAG", string("SWEEP"));

        uint256[] memory ciValues = _parseUintArray(vm.envOr("CI_VALUES", string("0,500000000000000000,1000000000000000000")));
        uint256[] memory asValues = _parseUintArray(vm.envOr("AS_VALUES", string("100000000000000000,500000000000000000,1000000000000000000")));
        uint256[] memory awValues = _parseUintArray(vm.envOr("AW_VALUES", string("30,50,80")));
        uint256[] memory ddValues = _parseUintArray(vm.envOr("DD_VALUES", string("200000000000000000,1000000000000000000")));
        uint256[] memory bbValues = _parseUintArray(vm.envOr("BB_VALUES", string("60,80,100")));

        uint256 totalCombos = ciValues.length * asValues.length * awValues.length * ddValues.length * bbValues.length;
        console2.log("=== Parameter Sweep ===");
        console2.log("Combinations:", totalCombos);
        console2.log("Runs/combo:", cfgRunsPerCombo);
        console2.log("Trades/run:", cfgTradesPerRun);
        console2.log("Uncapped:", cfgUncapped);
        console2.log("Trade range (ETH):", cfgMinBuy, cfgMaxBuy);

        _initInfrastructure();

        summaryFile = string(abi.encodePacked("analysis/sweep-", tag, "-summary.csv"));
        vm.writeFile(summaryFile, "ci,anchor_share,anchor_width,discovery_depth,buy_bias,worst_trader_pnl,best_trader_pnl,any_lm_loss,lm_eth_delta\n");

        uint256 comboIndex = 0;
        for (uint256 ci_i = 0; ci_i < ciValues.length; ci_i++) {
            for (uint256 as_i = 0; as_i < asValues.length; as_i++) {
                for (uint256 aw_i = 0; aw_i < awValues.length; aw_i++) {
                    for (uint256 dd_i = 0; dd_i < ddValues.length; dd_i++) {
                        for (uint256 bb_i = 0; bb_i < bbValues.length; bb_i++) {
                            comboIndex++;
                            _runCombo(
                                ciValues[ci_i], asValues[as_i], uint24(awValues[aw_i] > 100 ? 100 : awValues[aw_i]), ddValues[dd_i], bbValues[bb_i], comboIndex
                            );
                        }
                    }
                }
            }
        }

        console2.log("=== Sweep Complete ===");
        console2.log("Results:", summaryFile);
    }

    function _runCombo(uint256 ci, uint256 as_, uint24 aw, uint256 dd, uint256 bb, uint256 comboIdx) internal {
        optimizer = new ConfigurableOptimizer(ci, as_, aw, dd);
        cfgBuyBias = bb;

        // Setup environment once — VWAP accumulates across all runs
        _setupEnvironment(address(optimizer), comboIdx % 2 == 0, cfgUncapped);

        int256 worstPnl = type(int256).min;
        int256 bestPnl = type(int256).max;
        bool anyLoss = false;

        uint256 lmSystemStart = _getLmSystemEth();

        for (uint256 runIdx = 0; runIdx < cfgRunsPerCombo; runIdx++) {
            RunResult memory result = _executeRun(runIdx);

            int256 pnl = int256(result.traderFinalEth) - int256(result.traderInitEth);

            if (pnl > worstPnl) worstPnl = pnl;
            if (pnl < bestPnl) bestPnl = pnl;
            if (pnl > 0) anyLoss = true;
        }

        uint256 lmSystemEnd = _getLmSystemEth();
        int256 lmDelta = int256(lmSystemEnd) - int256(lmSystemStart);

        // Write summary row
        string memory row = string(abi.encodePacked(vm.toString(ci), ",", vm.toString(as_), ",", vm.toString(uint256(aw)), ","));
        row = string(abi.encodePacked(row, vm.toString(dd), ",", vm.toString(bb), ",", vm.toString(worstPnl), ","));
        row = string(abi.encodePacked(row, vm.toString(bestPnl), ",", anyLoss ? "true" : "false", ",", vm.toString(lmDelta)));
        vm.writeLine(summaryFile, row);

        if (anyLoss) {
            console2.log("UNSAFE combo", comboIdx);
            console2.log("  worstPnl:", worstPnl);
            console2.log("  lmDelta:", lmDelta);
        } else if (comboIdx % 5 == 0) {
            console2.log("Progress:", comboIdx);
        }
    }

    function _executeRun(uint256 runIndex) internal returns (RunResult memory result) {
        // DON'T re-setup environment — reuse existing so VWAP accumulates
        result.lmEthInit = _getLmSystemEth();

        // Clean up leftover tokens from previous run to prevent PnL leakage
        _cleanupTraderTokens();

        // Fund trader fresh each run
        vm.deal(trader, LM_FUNDING_ETH);
        vm.prank(trader);
        weth.deposit{ value: LM_FUNDING_ETH }();
        result.traderInitEth = weth.balanceOf(trader);

        for (uint256 i = 0; i < cfgTradesPerRun; i++) {
            if (uint256(keccak256(abi.encodePacked(runIndex, i, "recenter"))) % 3 == 0) {
                if (_tryRecenter()) result.numRecenters++;
            }

            uint256 rand = uint256(keccak256(abi.encodePacked(runIndex, i))) % 100;
            if (rand < cfgBuyBias) {
                uint256 range = cfgMaxBuy - cfgMinBuy;
                uint256 amount = (cfgMinBuy * 1 ether) + (uint256(keccak256(abi.encodePacked(runIndex, i, "buy"))) % (range * 1 ether));
                if (!_executeBuy(amount)) result.numBuysFailed++;
            } else {
                uint256 krkBal = kraiken.balanceOf(trader);
                if (krkBal > 0) {
                    uint256 pct = SELL_PCT_MIN + (uint256(keccak256(abi.encodePacked(runIndex, i, "sell"))) % SELL_PCT_RANGE);
                    uint256 amount = krkBal * pct / 100;
                    _executeSell(amount);
                }
            }
        }

        _tryRecenter();
        _liquidateTraderHoldings();
        _recoverStuckTokens();

        result.lmEthFinal = _getLmSystemEth();
        result.traderFinalEth = weth.balanceOf(trader);
    }
}
