#!/bin/bash
set -euo pipefail

# Portable root detection - works anywhere
ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
LOGS="$ROOT/logs"
REPRO="$ROOT/repro"
mkdir -p "$LOGS"

cd "$ROOT"

# Create scratch Go module directory
SCRATCH="$ROOT/scratch_cache_repro"
rm -rf "$SCRATCH"
mkdir -p "$SCRATCH"
cd "$SCRATCH"

# Initialize Go module
go mod init cache_repro

# Write the reproduction Go program
cat > main.go << 'GOEOF'
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/gofiber/fiber/v3"
	"github.com/gofiber/fiber/v3/middleware/cache"
)

func main() {
	app := fiber.New()

	// Health endpoint BEFORE cache middleware to avoid cache pollution
	app.Get("/health", func(c fiber.Ctx) error {
		return c.SendString("ok")
	})

	// Use default cache middleware (default KeyGenerator is the issue)
	app.Use(cache.New())

	// Handler returns the "id" query parameter value
	app.Get("/", func(c fiber.Ctx) error {
		return c.SendString(c.Query("id", "none"))
	})

	// Start server in background
	go func() {
		_ = app.Listen(":3001")
	}()

	// Poll for server readiness via health endpoint (up to 5s)
	client := &http.Client{Timeout: 2 * time.Second}
	for i := 0; i < 50; i++ {
		resp, err := client.Get("http://127.0.0.1:3001/health")
		if err == nil {
			resp.Body.Close()
			break
		}
		time.Sleep(100 * time.Millisecond)
	}

	// Make the two test requests to the cached endpoint
	resp1, err := client.Get("http://127.0.0.1:3001/?id=1")
	if err != nil {
		fmt.Println("ERROR request 1:", err)
		os.Exit(1)
	}
	body1, _ := io.ReadAll(resp1.Body)
	resp1.Body.Close()

	resp2, err := client.Get("http://127.0.0.1:3001/?id=2")
	if err != nil {
		fmt.Println("ERROR request 2:", err)
		os.Exit(1)
	}
	body2, _ := io.ReadAll(resp2.Body)
	resp2.Body.Close()

	// Write results to file to avoid stdout banner pollution
	outPath := os.Args[1]
	f, err := os.Create(outPath)
	if err != nil {
		fmt.Println("ERROR creating output file:", err)
		os.Exit(1)
	}
	defer f.Close()
	f.WriteString(strings.TrimSpace(string(body1)) + "\n")
	f.WriteString(strings.TrimSpace(string(body2)) + "\n")

	_ = app.Shutdown()
}
GOEOF

# Helper to run a version and capture output
run_version() {
	local version="$1"
	local out_file="$2"
	local log_file="$3"

	cd "$SCRATCH"
	go get "github.com/gofiber/fiber/v3@${version}" >>"$log_file" 2>&1
	go mod tidy >>"$log_file" 2>&1
	go build -o repro_bin main.go >>"$log_file" 2>&1
	./repro_bin "$out_file" >>"$log_file" 2>&1 || true
}

# Run against vulnerable version
run_version "v3.1.0" "$LOGS/vulnerable_output.txt" "$LOGS/vulnerable_build.log"

# Run against fixed version
run_version "v3.2.0" "$LOGS/fixed_output.txt" "$LOGS/fixed_build.log"

# Analyze results
echo "=== Vulnerable (v3.1.0) output ===" | tee "$LOGS/summary.txt"
cat "$LOGS/vulnerable_output.txt" | tee -a "$LOGS/summary.txt"

echo "" | tee -a "$LOGS/summary.txt"
echo "=== Fixed (v3.2.0) output ===" | tee -a "$LOGS/summary.txt"
cat "$LOGS/fixed_output.txt" | tee -a "$LOGS/summary.txt"

# Check for confirmation
V1=$(head -n1 "$LOGS/vulnerable_output.txt" || echo "")
V2=$(tail -n1 "$LOGS/vulnerable_output.txt" || echo "")
F1=$(head -n1 "$LOGS/fixed_output.txt" || echo "")
F2=$(tail -n1 "$LOGS/fixed_output.txt" || echo "")

echo "" | tee -a "$LOGS/summary.txt"
# Vulnerable indicator: both responses are identical (cache collision)
if [ "$V1" = "$V2" ]; then
	echo "VULNERABLE CONFIRMED: both requests returned identical cached value '$V1' (cache key collision ignores query string)" | tee -a "$LOGS/summary.txt"
	VULN_CONFIRMED=1
else
	echo "VULNERABLE NOT CONFIRMED: expected identical values, got '$V1' then '$V2'" | tee -a "$LOGS/summary.txt"
	VULN_CONFIRMED=0
fi

# Fixed indicator: responses differ (cache distinguishes requests)
if [ "$F1" != "$F2" ]; then
	echo "FIX CONFIRMED: requests returned different values '$F1' and '$F2' (cache key includes query string)" | tee -a "$LOGS/summary.txt"
	FIX_CONFIRMED=1
else
	echo "FIX NOT CONFIRMED: expected different values, got '$F1' then '$F2'" | tee -a "$LOGS/summary.txt"
	FIX_CONFIRMED=0
fi

# Build verdict
VERDICT="unconfirmed"
if [ "$VULN_CONFIRMED" -eq 1 ] && [ "$FIX_CONFIRMED" -eq 1 ]; then
	VERDICT="confirmed"
fi

# Write runtime manifest
cat > "$REPRO/runtime_manifest.json" <<EOF
{
  "vulnerable_version": "v3.1.0",
  "fixed_version": "v3.2.0",
  "vulnerable_results": {
    "request_1_id": "1",
    "request_1_response": "$V1",
    "request_2_id": "2",
    "request_2_response": "$V2",
    "indicator": "cache_collision"
  },
  "fixed_results": {
    "request_1_id": "1",
    "request_1_response": "$F1",
    "request_2_id": "2",
    "request_2_response": "$F2",
    "indicator": "cache_distinguishes_requests"
  },
  "verdict": "$VERDICT"
}
EOF

# Exit code: 0 if vulnerability confirmed and fix verified
if [ "$VERDICT" = "confirmed" ]; then
	echo "SUCCESS: vulnerability reproduced and fix verified" | tee -a "$LOGS/summary.txt"
	exit 0
else
	echo "FAILURE: could not confirm vulnerability or fix" | tee -a "$LOGS/summary.txt"
	exit 1
fi
