#!/usr/bin/env bash
# =============================================================================
# deploy-optimizer.sh — Unified Push3 → deploy pipeline
#
# Pipeline: Push3 file → transpiler → Solidity → forge compile → UUPS upgrade
#
# Usage:
#   ./tools/deploy-optimizer.sh [--live] <input.push3>
#
# Flags:
#   --live   Target mainnet/testnet (requires OPTIMIZER_PROXY and RPC_URL env vars).
#            Without this flag the script targets a local Anvil instance.
#
# Environment (--live mode only):
#   OPTIMIZER_PROXY   Address of the deployed UUPS proxy to upgrade.
#   RPC_URL           JSON-RPC endpoint.
#   SECRET_FILE       Path to seed-phrase file (default: onchain/.secret).
#
# Environment (dry-run mode only):
#   ANVIL_FORK_URL    Required when Anvil is not already running.  Must point to
#                     a Base RPC endpoint so that forked state includes Uniswap V3
#                     Factory and WETH.
# =============================================================================

set -euo pipefail

# Foundry tools (forge, cast, anvil)
export PATH="${HOME}/.foundry/bin:${PATH}"

# ---------------------------------------------------------------------------
# Paths
# ---------------------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
ONCHAIN_DIR="$REPO_ROOT/onchain"
TRANSPILER_DIR="$SCRIPT_DIR/push3-transpiler"
TRANSPILER_OUT="$ONCHAIN_DIR/src/OptimizerV3Push3.sol"

# ---------------------------------------------------------------------------
# Parse arguments
# ---------------------------------------------------------------------------
LIVE=false
PUSH3_FILE=""

for arg in "$@"; do
    case "$arg" in
        --live) LIVE=true ;;
        --*) echo "Error: Unknown option: $arg" >&2; exit 1 ;;
        *) PUSH3_FILE="$arg" ;;
    esac
done

if [ -z "$PUSH3_FILE" ]; then
    echo "Usage: $0 [--live] <input.push3>" >&2
    exit 1
fi

if [ ! -f "$PUSH3_FILE" ]; then
    echo "Error: File not found: $PUSH3_FILE" >&2
    exit 1
fi

# Make PUSH3_FILE absolute so it works regardless of cwd changes
PUSH3_FILE="$(cd "$(dirname "$PUSH3_FILE")" && pwd)/$(basename "$PUSH3_FILE")"

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
info()    { echo "  [info]  $*"; }
success() { echo "  [ok]    $*"; }
step()    { echo; echo "==> $*"; }
fail()    { echo; echo "  [fail]  $*" >&2; exit 1; }

# Decode a uint256 returned by cast call (strips 0x prefix, converts hex→dec)
decode_uint() {
    python3 -c "print(int('$1', 16))" 2>/dev/null || echo "0"
}

# Decode a bool returned by cast call
decode_bool() {
    python3 -c "print('true' if int('$1', 16) != 0 else 'false')" 2>/dev/null || echo "false"
}

# Cleanup state
ANVIL_PID=""
SECRET_CREATED=false

cleanup() {
    if [ -n "$ANVIL_PID" ]; then
        kill "$ANVIL_PID" 2>/dev/null || true
    fi
    if $SECRET_CREATED && [ -f "$ONCHAIN_DIR/.secret" ]; then
        rm -f "$ONCHAIN_DIR/.secret"
    fi
    rm -f /tmp/deploy-local-output.txt /tmp/new-optimizer-impl.txt \
          /tmp/push3-test-addr.txt /tmp/upgrade-output.txt 2>/dev/null || true
}
trap cleanup EXIT

# ---------------------------------------------------------------------------
# Step 0 — Validate tooling
# ---------------------------------------------------------------------------
step "Checking required tools"

for tool in forge cast npx node python3; do
    if ! command -v "$tool" &>/dev/null; then
        fail "$tool not found in PATH"
    fi
done

if ! $LIVE; then
    if ! command -v anvil &>/dev/null; then
        fail "anvil not found in PATH (required for dry-run mode)"
    fi
fi
success "Required tools are present"

# ---------------------------------------------------------------------------
# Step 1 — Transpile Push3 → Solidity
# ---------------------------------------------------------------------------
step "Transpiling $(basename "$PUSH3_FILE") → OptimizerV3Push3.sol"

(
    cd "$TRANSPILER_DIR"
    if [ ! -d node_modules ]; then
        info "Installing transpiler dependencies..."
        npm install --silent
    fi
    npx tsx src/index.ts "$PUSH3_FILE" "$TRANSPILER_OUT"
)

success "Generated $TRANSPILER_OUT"

# ---------------------------------------------------------------------------
# Step 2 — Compile with forge
# ---------------------------------------------------------------------------
step "Compiling contracts (forge build)"

(
    cd "$ONCHAIN_DIR"
    forge build --silent
)

success "Compilation succeeded"

# ---------------------------------------------------------------------------
# Step 3 — Setup network target
# ---------------------------------------------------------------------------
step "Setting up network target"

OPTIMIZER_PROXY="${OPTIMIZER_PROXY:-}"

if $LIVE; then
    # ---- Live / testnet mode ----
    info "Mode: LIVE (mainnet/testnet)"

    RPC_URL="${RPC_URL:-}"
    if [ -z "$RPC_URL" ]; then
        fail "--live requires RPC_URL env var"
    fi
    if [ -z "$OPTIMIZER_PROXY" ]; then
        fail "--live requires OPTIMIZER_PROXY env var"
    fi

    SECRET_FILE="${SECRET_FILE:-$ONCHAIN_DIR/.secret}"
    if [ ! -f "$SECRET_FILE" ]; then
        fail "Secret file not found: $SECRET_FILE (set SECRET_FILE env var)"
    fi

    cast chain-id --rpc-url "$RPC_URL" >/dev/null 2>&1 || \
        fail "Cannot reach RPC endpoint: $RPC_URL"

    CHAIN_ID="$(cast chain-id --rpc-url "$RPC_URL")"
    info "Connected to chain $CHAIN_ID via $RPC_URL"
    info "Target proxy: $OPTIMIZER_PROXY"
    info "Key file: $SECRET_FILE"
else
    # ---- Dry-run (Anvil) mode ----
    info "Mode: DRY-RUN (local Anvil)"
    RPC_URL="http://localhost:8545"

    # Ensure onchain/.secret exists for UpgradeOptimizer.sol (uses vm.readFile)
    if [ ! -f "$ONCHAIN_DIR/.secret" ]; then
        cp "$ONCHAIN_DIR/.secret.local" "$ONCHAIN_DIR/.secret"
        SECRET_CREATED=true
        info "Created temporary onchain/.secret from .secret.local"
    fi
    SECRET_FILE="$ONCHAIN_DIR/.secret"

    # Check if Anvil is already running.
    #
    # DeployLocal.sol depends on live Base infrastructure: Uniswap V3 Factory at
    # 0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24 and WETH at
    # 0x4200000000000000000000000000000000000006.  A plain (unfork'd) Anvil has
    # neither, so cold-starting without --fork-url silently breaks the pipeline.
    #
    # When Anvil is already running (dev stack or CI), we use it as-is.
    # When it is not running we require ANVIL_FORK_URL and start a forked instance.
    if cast chain-id --rpc-url "$RPC_URL" >/dev/null 2>&1; then
        info "Anvil already running at $RPC_URL"
    else
        ANVIL_FORK_URL="${ANVIL_FORK_URL:-}"
        if [ -z "$ANVIL_FORK_URL" ]; then
            fail "Anvil is not running at $RPC_URL and ANVIL_FORK_URL is not set.
  DeployLocal.sol requires Base network contracts (Uniswap V3 Factory, WETH).
  Either start a Base-forked Anvil externally, or set ANVIL_FORK_URL to a Base
  RPC endpoint (e.g. ANVIL_FORK_URL=https://mainnet.base.org)."
        fi

        info "Starting Anvil (fork: $ANVIL_FORK_URL)..."
        anvil --silent \
              --fork-url "$ANVIL_FORK_URL" \
              --mnemonic "test test test test test test test test test test test junk" \
              --port 8545 &
        ANVIL_PID=$!
        # Poll until ready (no fixed sleeps)
        TRIES=0
        until cast chain-id --rpc-url "$RPC_URL" >/dev/null 2>&1; do
            TRIES=$((TRIES + 1))
            [ $TRIES -gt 50 ] && fail "Anvil did not start within 50 attempts"
            sleep 0.2
        done
        info "Anvil started (PID $ANVIL_PID, fork: $ANVIL_FORK_URL)"
    fi

    # If no OPTIMIZER_PROXY set, deploy a fresh local stack
    if [ -z "$OPTIMIZER_PROXY" ]; then
        info "No OPTIMIZER_PROXY set — deploying fresh local stack via DeployLocal.sol"
        (
            cd "$ONCHAIN_DIR"
            forge script script/DeployLocal.sol --tc DeployLocal \
                --rpc-url "$RPC_URL" \
                --broadcast 2>&1 | tee /tmp/deploy-local-output.txt
        )

        CHAIN_ID="$(cast chain-id --rpc-url "$RPC_URL")"
        BROADCAST_JSON="$ONCHAIN_DIR/broadcast/DeployLocal.sol/$CHAIN_ID/run-latest.json"

        if [ -f "$BROADCAST_JSON" ]; then
            OPTIMIZER_PROXY="$(python3 - "$BROADCAST_JSON" <<'PYEOF'
import json, sys
path = sys.argv[1]
with open(path) as f:
    data = json.load(f)
txs = data.get('transactions', [])
# The ERC1967Proxy is the optimizer proxy
for tx in txs:
    name = (tx.get('contractName') or '').lower()
    if 'erc1967proxy' in name:
        print(tx.get('contractAddress', ''))
        sys.exit(0)
# Fallback: look for "Optimizer:" in the deploy output
print('')
PYEOF
)"
        fi

        # Fallback: grep the console log output
        if [ -z "$OPTIMIZER_PROXY" ]; then
            OPTIMIZER_PROXY="$(grep -oE 'Optimizer: 0x[0-9a-fA-F]{40}' \
                /tmp/deploy-local-output.txt | awk '{print $2}' | tail -1 || true)"
        fi

        if [ -z "$OPTIMIZER_PROXY" ]; then
            fail "Could not determine OPTIMIZER_PROXY from fresh deployment. Set OPTIMIZER_PROXY manually."
        fi
        info "Fresh stack deployed. Optimizer proxy: $OPTIMIZER_PROXY"

        # Verify that the seed trade bootstrapped VWAP during deployment.
        # DeployLocal.sol runs a first recenter + seed buy + second recenter so that
        # cumulativeVolume>0 before any user can interact with the protocol.
        if [ ! -f "$BROADCAST_JSON" ]; then
            fail "Broadcast JSON not found: $BROADCAST_JSON — cannot verify VWAP bootstrap"
        fi
        LM_ADDR=""
        LM_ADDR="$(python3 - "$BROADCAST_JSON" <<'PYEOF'
import json, sys
with open(sys.argv[1]) as f:
    data = json.load(f)
for tx in data.get('transactions', []):
    if (tx.get('contractName') or '').lower() == 'liquiditymanager':
        print(tx.get('contractAddress', ''))
        break
PYEOF
)"
        if [ -z "$LM_ADDR" ]; then
            info "WARNING: LiquidityManager address not found in $BROADCAST_JSON — skipping VWAP check"
        fi
        if [ -n "$LM_ADDR" ]; then
            CUMVOL_HEX="$(cast call "$LM_ADDR" "cumulativeVolume()(uint256)" \
                --rpc-url "$RPC_URL" 2>/dev/null || echo "0x0")"
            CUMVOL="$(decode_uint "$CUMVOL_HEX")"
            if [ "$CUMVOL" -gt 0 ]; then
                success "VWAP bootstrapped: LiquidityManager.cumulativeVolume=$CUMVOL"
            else
                fail "VWAP not bootstrapped: cumulativeVolume=0 — seed trade may have failed"
            fi
        fi
    fi
fi

# Derive private key for forge create / cast call operations
SEED="$(cat "$SECRET_FILE")"
DEPLOYER_KEY="$(cast wallet derive-private-key "$SEED" 0)"
DEPLOYER_ADDR="$(cast wallet address --private-key "$DEPLOYER_KEY")"
info "Deployer: $DEPLOYER_ADDR"

# ---------------------------------------------------------------------------
# Step 4 — Capture pre-upgrade state
# ---------------------------------------------------------------------------
step "Capturing pre-upgrade optimizer parameters"

# calculateSentiment(averageTaxRate, percentageStaked) is public pure — safe on both
# old and new implementations without needing a live Stake contract.
#
# Reference input: 95% staked (95e16), 5% tax rate (5e16)
REF_STAKED="950000000000000000"
REF_TAXRATE="50000000000000000"

PRE_RAW="$(cast call "$OPTIMIZER_PROXY" \
    "calculateSentiment(uint256,uint256)(uint256)" \
    "$REF_TAXRATE" "$REF_STAKED" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "0x0")"
PRE_SENTIMENT="$(decode_uint "$PRE_RAW")"
info "Pre-upgrade calculateSentiment(staked=$REF_STAKED, tax=$REF_TAXRATE) = $PRE_SENTIMENT"

# ---------------------------------------------------------------------------
# Step 5 — Deploy new implementation (for diff preview only)
# ---------------------------------------------------------------------------
step "Deploying new Optimizer implementation for diff preview"

(
    cd "$ONCHAIN_DIR"
    forge create src/OptimizerV3.sol:OptimizerV3 \
        --rpc-url "$RPC_URL" \
        --private-key "$DEPLOYER_KEY" \
        --json 2>/dev/null \
        | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('deployedTo',''))" \
        > /tmp/new-optimizer-impl.txt
)

NEW_IMPL="$(cat /tmp/new-optimizer-impl.txt 2>/dev/null || echo "")"
[ -z "$NEW_IMPL" ] && fail "Failed to deploy new OptimizerV3 implementation"
info "New implementation deployed at: $NEW_IMPL"

# calculateSentiment is pure — callable on bare (uninitialized) implementation
NEW_RAW="$(cast call "$NEW_IMPL" \
    "calculateSentiment(uint256,uint256)(uint256)" \
    "$REF_TAXRATE" "$REF_STAKED" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "0x0")"
NEW_SENTIMENT="$(decode_uint "$NEW_RAW")"
info "New impl calculateSentiment(staked=$REF_STAKED, tax=$REF_TAXRATE) = $NEW_SENTIMENT"

# ---------------------------------------------------------------------------
# Step 6 — Show parameter diff before upgrading
# ---------------------------------------------------------------------------
step "Parameter diff (before upgrade confirmation)"

echo
echo "  ┌──────────────────────────────────────────────────────────────"
echo "  │  calculateSentiment(averageTaxRate=$REF_TAXRATE, percentageStaked=$REF_STAKED)"
echo "  ├──────────────────────────────────────────────────────────────"
printf "  │  Old (via proxy)       : %s\n" "$PRE_SENTIMENT"
printf "  │  New (new impl)        : %s\n" "$NEW_SENTIMENT"
if [ "$PRE_SENTIMENT" = "$NEW_SENTIMENT" ]; then
    echo "  │  Diff                  : none — implementations are semantically equivalent"
else
    echo "  │  Diff                  : CHANGED"
fi
echo "  └──────────────────────────────────────────────────────────────"
echo

if $LIVE; then
    printf "  Proceed with upgrade on LIVE network? [y/N] "
    read -r CONFIRM
    case "$CONFIRM" in
        y|Y|yes|YES) ;;
        *) echo "Upgrade cancelled."; exit 0 ;;
    esac
fi

# ---------------------------------------------------------------------------
# Step 7 — Run UUPS upgrade via UpgradeOptimizer.sol
# ---------------------------------------------------------------------------
step "Running UUPS upgrade (UpgradeOptimizer.sol)"

(
    cd "$ONCHAIN_DIR"
    OPTIMIZER_PROXY="$OPTIMIZER_PROXY" \
    forge script script/UpgradeOptimizer.sol \
        --rpc-url "$RPC_URL" \
        --broadcast 2>&1 | tee /tmp/upgrade-output.txt
) || fail "UpgradeOptimizer.sol script failed"

success "Proxy upgraded"

# Confirm new implementation address from broadcast log
UPGRADED_IMPL="$(grep -oE 'New Optimizer implementation: 0x[0-9a-fA-F]{40}' \
    /tmp/upgrade-output.txt | awk '{print $NF}' | tail -1 || true)"
[ -n "$UPGRADED_IMPL" ] && info "Upgraded implementation: $UPGRADED_IMPL"

# ---------------------------------------------------------------------------
# Step 8 — Round-trip verification
# ---------------------------------------------------------------------------
step "Round-trip verification (OptimizerV3Push3 isBullMarket)"

# Deploy the transpiled OptimizerV3Push3 as a standalone test fixture.
# This contract has no constructor dependencies (pure functions only).
(
    cd "$ONCHAIN_DIR"
    forge create src/OptimizerV3Push3.sol:OptimizerV3Push3 \
        --rpc-url "$RPC_URL" \
        --private-key "$DEPLOYER_KEY" \
        --json 2>/dev/null \
        | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('deployedTo',''))" \
        > /tmp/push3-test-addr.txt
)

PUSH3_ADDR="$(cat /tmp/push3-test-addr.txt 2>/dev/null || echo "")"
[ -z "$PUSH3_ADDR" ] && fail "Failed to deploy OptimizerV3Push3 for round-trip verification"
info "OptimizerV3Push3 test fixture: $PUSH3_ADDR"

# Test vectors derived from the Push3 program semantics:
#
#   isBullMarket(percentageStaked, averageTaxRate) →
#     if stakedPct ≤ 91%          → false  (always bear)
#     else penalty = deltaS³ × effIdx / 20
#          if penalty < 50        → true   (bull)
#          else                   → false  (bear)
#
# Vector 1 — Bear by staked threshold:
#   90% staked, any tax → stakedPct=90 ≤ 91 → false
#
# Vector 2 — Bear by penalty:
#   92% staked (deltaS=8), tax=1e17 → rawIdx=20, effIdx=21,
#   penalty = 512×21/20 = 537 ≥ 50  → false
#
# Vector 3 — Bull:
#   99% staked (deltaS=1), tax=0 → rawIdx=0, effIdx=0,
#   penalty = 1×0/20 = 0 < 50     → true

PASS=true
VERIFY_LOG=""

run_vector() {
    local label="$1" pct="$2" tax="$3" expected="$4"
    local raw actual
    raw="$(cast call "$PUSH3_ADDR" \
        "isBullMarket(uint256,uint256)(bool)" \
        "$pct" "$tax" \
        --rpc-url "$RPC_URL" 2>/dev/null || echo "0x0")"
    actual="$(decode_bool "$raw")"
    if [ "$actual" = "$expected" ]; then
        VERIFY_LOG="$VERIFY_LOG\n  [PASS]  $label"
    else
        VERIFY_LOG="$VERIFY_LOG\n  [FAIL]  $label (got=$actual expected=$expected)"
        PASS=false
    fi
}

run_vector "Bear/threshold  isBullMarket(90e16, 0)    → false" \
    "900000000000000000" "0" "false"
run_vector "Bear/penalty    isBullMarket(92e16, 1e17) → false" \
    "920000000000000000" "100000000000000000" "false"
run_vector "Bull/zero-tax   isBullMarket(99e16, 0)    → true" \
    "990000000000000000" "0" "true"

echo
printf "%b\n" "$VERIFY_LOG"
echo

if $PASS; then
    success "Round-trip verification passed"
else
    fail "Round-trip verification FAILED — transpiled isBullMarket does not match expected values"
fi

# ---------------------------------------------------------------------------
# Summary
# ---------------------------------------------------------------------------
echo
echo "==> Pipeline complete"
echo "    Push3 source : $(basename "$PUSH3_FILE")"
echo "    Transpiled   : $TRANSPILER_OUT"
echo "    Proxy        : $OPTIMIZER_PROXY"
echo "    Network      : $RPC_URL"
exit 0
