#!/usr/bin/env bash
set -euo pipefail

# Brief: Full integration test for Cerberus security stack
# Tests all security features working together:
# - WAF (Coraza) for payload inspection
# - Rate Limiting for volume abuse prevention
# - Security handler ordering in Caddy config
#
# Test Cases:
# - TC-1: Verify all features enabled via /api/v1/security/status
# - TC-2: Verify handler order in Caddy config
# - TC-3: WAF blocking doesn't consume rate limit quota
# - TC-4: Legitimate traffic flows through all layers
# - TC-5: Basic latency check

# Ensure we operate from repo root
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$PROJECT_ROOT"

# ============================================================================
# Configuration
# ============================================================================
CONTAINER_NAME="charon-cerberus-test"
BACKEND_CONTAINER="cerberus-backend"
TEST_DOMAIN="cerberus.test.local"

# Use unique non-conflicting ports
API_PORT=8480
HTTP_PORT=8481
HTTPS_PORT=8444
CADDY_ADMIN_PORT=2319

# Rate limit config for testing
RATE_LIMIT_REQUESTS=5
RATE_LIMIT_WINDOW_SEC=30

# ============================================================================
# Colors for output
# ============================================================================
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_test() { echo -e "${BLUE}[TEST]${NC} $1"; }

# ============================================================================
# Test counters
# ============================================================================
PASSED=0
FAILED=0

pass_test() {
    PASSED=$((PASSED + 1))
    echo -e "  ${GREEN}✓ PASS${NC}"
}

fail_test() {
    FAILED=$((FAILED + 1))
    echo -e "  ${RED}✗ FAIL${NC}: $1"
}

# Assert HTTP status code
assert_http() {
    local expected=$1
    local actual=$2
    local desc=$3
    if [ "$actual" = "$expected" ]; then
        log_info "  ✓ $desc: HTTP $actual"
        PASSED=$((PASSED + 1))
    else
        log_error "  ✗ $desc: HTTP $actual (expected $expected)"
        FAILED=$((FAILED + 1))
    fi
}

# ============================================================================
# Helper Functions
# ============================================================================

# Dumps debug information on failure
on_failure() {
    local exit_code=$?
    echo ""
    echo "=============================================="
    echo "=== FAILURE DEBUG INFO (exit code: $exit_code) ==="
    echo "=============================================="
    echo ""

    echo "=== Charon API Logs (last 150 lines) ==="
    docker logs ${CONTAINER_NAME} 2>&1 | tail -150 || echo "Could not retrieve container logs"
    echo ""

    echo "=== Caddy Admin API Config ==="
    curl -sL "http://localhost:${CADDY_ADMIN_PORT}/config/" 2>/dev/null | head -300 || echo "Could not retrieve Caddy config"
    echo ""

    echo "=== Security Config in API ==="
    curl -s -b "${TMP_COOKIE:-/dev/null}" "http://localhost:${API_PORT}/api/v1/security/config" 2>/dev/null || echo "Could not retrieve security config"
    echo ""

    echo "=== Security Status ==="
    curl -s -b "${TMP_COOKIE:-/dev/null}" "http://localhost:${API_PORT}/api/v1/security/status" 2>/dev/null || echo "Could not retrieve security status"
    echo ""

    echo "=== Security Rulesets ==="
    curl -s -b "${TMP_COOKIE:-/dev/null}" "http://localhost:${API_PORT}/api/v1/security/rulesets" 2>/dev/null || echo "Could not retrieve rulesets"
    echo ""

    echo "=============================================="
    echo "=== END DEBUG INFO ==="
    echo "=============================================="
}

# Cleanup function
cleanup() {
    log_info "Cleaning up test resources..."
    docker rm -f ${CONTAINER_NAME} 2>/dev/null || true
    docker rm -f ${BACKEND_CONTAINER} 2>/dev/null || true
    rm -f "${TMP_COOKIE:-}" 2>/dev/null || true
    log_info "Cleanup complete"
}

# Set up trap to dump debug info on any error and always cleanup
trap on_failure ERR
trap cleanup EXIT

echo "=============================================="
echo "=== Cerberus Full Integration Test Starting ==="
echo "=============================================="
echo ""

# Check dependencies
if ! command -v docker >/dev/null 2>&1; then
    log_error "docker is not available; aborting"
    exit 1
fi

if ! command -v curl >/dev/null 2>&1; then
    log_error "curl is not available; aborting"
    exit 1
fi

# ============================================================================
# Step 1: Build image if needed
# ============================================================================
if ! docker image inspect charon:local >/dev/null 2>&1; then
    log_info "Building charon:local image..."
    docker build -t charon:local .
else
    log_info "Using existing charon:local image"
fi

# ============================================================================
# Step 2: Start containers
# ============================================================================
log_info "Stopping any existing test containers..."
docker rm -f ${CONTAINER_NAME} 2>/dev/null || true
docker rm -f ${BACKEND_CONTAINER} 2>/dev/null || true

# Ensure network exists
if ! docker network inspect containers_default >/dev/null 2>&1; then
    log_info "Creating containers_default network..."
    docker network create containers_default
fi

log_info "Starting httpbin backend container..."
docker run -d --name ${BACKEND_CONTAINER} --network containers_default kennethreitz/httpbin

log_info "Starting Charon container with ALL Cerberus features enabled..."
docker run -d --name ${CONTAINER_NAME} \
    --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
    --network containers_default \
    -p ${HTTP_PORT}:80 -p ${HTTPS_PORT}:443 -p ${API_PORT}:8080 -p ${CADDY_ADMIN_PORT}:2019 \
    -e CHARON_ENV=development \
    -e CHARON_DEBUG=1 \
    -e CHARON_HTTP_PORT=8080 \
    -e CHARON_DB_PATH=/app/data/charon.db \
    -e CHARON_FRONTEND_DIR=/app/frontend/dist \
    -e CHARON_CADDY_ADMIN_API=http://localhost:2019 \
    -e CHARON_CADDY_CONFIG_DIR=/app/data/caddy \
    -e CHARON_CADDY_BINARY=caddy \
    -e CERBERUS_SECURITY_CERBERUS_ENABLED=true \
    -e CHARON_SECURITY_WAF_MODE=block \
    -e CERBERUS_SECURITY_RATELIMIT_MODE=enabled \
    -e CERBERUS_SECURITY_ACL_ENABLED=true \
    -v charon_cerberus_test_data:/app/data \
    -v caddy_cerberus_test_data:/data \
    -v caddy_cerberus_test_config:/config \
    charon:local

log_info "Waiting for Charon API to be ready..."
for i in {1..30}; do
    if curl -s -f "http://localhost:${API_PORT}/api/v1/" >/dev/null 2>&1; then
        log_info "Charon API is ready"
        break
    fi
    if [ $i -eq 30 ]; then
        log_error "Charon API failed to start"
        exit 1
    fi
    echo -n '.'
    sleep 1
done
echo ""

log_info "Waiting for httpbin backend to be ready..."
for i in {1..20}; do
    if docker exec ${CONTAINER_NAME} sh -c "wget -q -O- http://${BACKEND_CONTAINER}/get 2>/dev/null || curl -s http://${BACKEND_CONTAINER}/get" >/dev/null 2>&1; then
        log_info "httpbin backend is ready"
        break
    fi
    if [ $i -eq 20 ]; then
        log_error "httpbin backend failed to start"
        exit 1
    fi
    echo -n '.'
    sleep 1
done
echo ""

# ============================================================================
# Step 3: Register user and authenticate
# ============================================================================
log_info "Registering admin user and logging in..."
TMP_COOKIE=$(mktemp)

curl -s -X POST -H "Content-Type: application/json" \
    -d '{"email":"cerberus-test@example.local","password":"password123","name":"Cerberus Tester"}' \
    "http://localhost:${API_PORT}/api/v1/auth/register" >/dev/null 2>&1 || true

curl -s -X POST -H "Content-Type: application/json" \
    -d '{"email":"cerberus-test@example.local","password":"password123"}' \
    -c "${TMP_COOKIE}" \
    "http://localhost:${API_PORT}/api/v1/auth/login" >/dev/null

log_info "Authentication complete"

# ============================================================================
# Step 4: Create proxy host
# ============================================================================
log_info "Creating proxy host '${TEST_DOMAIN}' pointing to backend..."
PROXY_HOST_PAYLOAD=$(cat <<EOF
{
    "name": "cerberus-test-backend",
    "domain_names": "${TEST_DOMAIN}",
    "forward_scheme": "http",
    "forward_host": "${BACKEND_CONTAINER}",
    "forward_port": 80,
    "enabled": true
}
EOF
)

CREATE_RESP=$(curl -s -w "\n%{http_code}" -X POST -H "Content-Type: application/json" \
    -d "${PROXY_HOST_PAYLOAD}" \
    -b "${TMP_COOKIE}" \
    "http://localhost:${API_PORT}/api/v1/proxy-hosts")
CREATE_STATUS=$(echo "$CREATE_RESP" | tail -n1)

if [ "$CREATE_STATUS" = "201" ]; then
    log_info "Proxy host created successfully"
else
    log_info "Proxy host may already exist (status: $CREATE_STATUS)"
fi

# Wait for Caddy to apply config
sleep 3

# ============================================================================
# Step 5: Create WAF ruleset (XSS protection)
# ============================================================================
log_info "Creating XSS WAF ruleset..."
XSS_RULESET=$(cat <<'EOF'
{
    "name": "cerberus-xss",
    "content": "SecRule REQUEST_BODY|ARGS|ARGS_NAMES \"<script\" \"id:99001,phase:2,deny,status:403,msg:'XSS Attack Detected'\""
}
EOF
)

XSS_RESP=$(curl -s -w "\n%{http_code}" -X POST -H "Content-Type: application/json" \
    -d "${XSS_RULESET}" \
    -b "${TMP_COOKIE}" \
    "http://localhost:${API_PORT}/api/v1/security/rulesets")
XSS_STATUS=$(echo "$XSS_RESP" | tail -n1)

if [ "$XSS_STATUS" = "200" ] || [ "$XSS_STATUS" = "201" ]; then
    log_info "XSS ruleset created"
else
    log_warn "XSS ruleset creation returned status: $XSS_STATUS"
fi

# ============================================================================
# Step 6: Enable WAF in block mode + configure rate limiting
# ============================================================================
log_info "Enabling WAF (block mode) and rate limiting (${RATE_LIMIT_REQUESTS} req / ${RATE_LIMIT_WINDOW_SEC} sec)..."
SECURITY_CONFIG=$(cat <<EOF
{
    "name": "default",
    "enabled": true,
    "waf_mode": "block",
    "waf_rules_source": "cerberus-xss",
    "rate_limit_enable": true,
    "rate_limit_requests": ${RATE_LIMIT_REQUESTS},
    "rate_limit_window_sec": ${RATE_LIMIT_WINDOW_SEC},
    "rate_limit_burst": 1,
    "admin_whitelist": "0.0.0.0/0"
}
EOF
)

SEC_RESP=$(curl -s -w "\n%{http_code}" -X POST -H "Content-Type: application/json" \
    -d "${SECURITY_CONFIG}" \
    -b "${TMP_COOKIE}" \
    "http://localhost:${API_PORT}/api/v1/security/config")
SEC_STATUS=$(echo "$SEC_RESP" | tail -n1)

if [ "$SEC_STATUS" = "200" ]; then
    log_info "Security configuration applied"
else
    log_warn "Security config returned status: $SEC_STATUS"
fi

# Wait for Caddy to reload with all security features
log_info "Waiting for Caddy to apply security configuration..."
sleep 5

echo ""
echo "=============================================="
echo "=== Running Cerberus Integration Test Cases ==="
echo "=============================================="
echo ""

# ============================================================================
# TC-1: Verify all features enabled via /api/v1/security/status
# ============================================================================
log_test "TC-1: Verify All Features Enabled"

STATUS_RESP=$(curl -s -b "${TMP_COOKIE}" "http://localhost:${API_PORT}/api/v1/security/status")

# Check Cerberus enabled (nested: "cerberus":{"enabled":true})
if echo "$STATUS_RESP" | grep -qE '"cerberus":\s*\{[^}]*"enabled":\s*true'; then
    log_info "  ✓ Cerberus enabled"
    PASSED=$((PASSED + 1))
else
    fail_test "Cerberus not enabled in status response"
fi

# Check WAF mode (nested: "waf":{"mode":"block",...})
if echo "$STATUS_RESP" | grep -qE '"waf":\s*\{[^}]*"mode":\s*"block"'; then
    log_info "  ✓ WAF mode is 'block'"
    PASSED=$((PASSED + 1))
else
    fail_test "WAF mode not set to 'block'"
fi

# Check rate limit enabled (nested: "rate_limit":{"enabled":true,...})
if echo "$STATUS_RESP" | grep -qE '"rate_limit":\s*\{[^}]*"enabled":\s*true'; then
    log_info "  ✓ Rate limit enabled"
    PASSED=$((PASSED + 1))
else
    fail_test "Rate limit not enabled"
fi

# ============================================================================
# TC-2: Verify handler order in Caddy config
# ============================================================================
log_test "TC-2: Verify Handler Order in Caddy Config"

CADDY_CONFIG=$(curl -sL "http://localhost:${CADDY_ADMIN_PORT}/config/" 2>/dev/null || echo "")

if [ -z "$CADDY_CONFIG" ]; then
    fail_test "Could not retrieve Caddy config"
else
    # Check for WAF handler
    if echo "$CADDY_CONFIG" | grep -q '"handler":"waf"'; then
        log_info "  ✓ WAF handler found in Caddy config"
        PASSED=$((PASSED + 1))
    else
        fail_test "WAF handler not found in Caddy config"
    fi

    # Check for rate_limit handler
    if echo "$CADDY_CONFIG" | grep -q '"handler":"rate_limit"'; then
        log_info "  ✓ rate_limit handler found in Caddy config"
        PASSED=$((PASSED + 1))
    else
        fail_test "rate_limit handler not found in Caddy config"
    fi

    # Check for reverse_proxy handler (should be last)
    if echo "$CADDY_CONFIG" | grep -q '"handler":"reverse_proxy"'; then
        log_info "  ✓ reverse_proxy handler found in Caddy config"
        PASSED=$((PASSED + 1))
    else
        fail_test "reverse_proxy handler not found in Caddy config"
    fi

    # Verify security handlers appear before reverse_proxy
    # Since Caddy JSON can be minified (one line), use byte offset approach
    WAF_POS=$(echo "$CADDY_CONFIG" | grep -ob '"handler":"waf"' | head -1 | cut -d: -f1 || echo "0")
    RATE_POS=$(echo "$CADDY_CONFIG" | grep -ob '"handler":"rate_limit"' | head -1 | cut -d: -f1 || echo "0")
    PROXY_POS=$(echo "$CADDY_CONFIG" | grep -ob '"handler":"reverse_proxy"' | head -1 | cut -d: -f1 || echo "0")

    if [ "$WAF_POS" != "0" ] && [ "$RATE_POS" != "0" ] && [ "$PROXY_POS" != "0" ]; then
        if [ "$WAF_POS" -lt "$PROXY_POS" ] && [ "$RATE_POS" -lt "$PROXY_POS" ]; then
            log_info "  ✓ Security handlers appear before reverse_proxy"
            PASSED=$((PASSED + 1))
        else
            fail_test "Security handlers not in correct order"
        fi
    else
        log_warn "  Could not determine exact handler positions (may be nested)"
        PASSED=$((PASSED + 1))
    fi
fi

# ============================================================================
# TC-3: WAF blocking doesn't consume rate limit quota
# ============================================================================
log_test "TC-3: WAF Blocking Doesn't Consume Rate Limit"

log_info "  Sending 3 malicious requests (should be blocked by WAF with 403)..."

WAF_BLOCKED=0
for i in 1 2 3; do
    CODE=$(curl -s -o /dev/null -w "%{http_code}" \
        -H "Host: ${TEST_DOMAIN}" \
        "http://localhost:${HTTP_PORT}/get?q=%3Cscript%3Ealert(1)%3C/script%3E")
    if [ "$CODE" = "403" ]; then
        WAF_BLOCKED=$((WAF_BLOCKED + 1))
        log_info "    Malicious request $i: HTTP $CODE (WAF blocked) ✓"
    else
        log_warn "    Malicious request $i: HTTP $CODE (expected 403)"
    fi
done

if [ $WAF_BLOCKED -eq 3 ]; then
    log_info "  ✓ All 3 malicious requests blocked by WAF"
    PASSED=$((PASSED + 1))
else
    fail_test "Not all malicious requests were blocked by WAF ($WAF_BLOCKED/3)"
fi

log_info "  Sending ${RATE_LIMIT_REQUESTS} legitimate requests (should all succeed with 200)..."

LEGIT_SUCCESS=0
for i in $(seq 1 ${RATE_LIMIT_REQUESTS}); do
    CODE=$(curl -s -o /dev/null -w "%{http_code}" \
        -H "Host: ${TEST_DOMAIN}" \
        "http://localhost:${HTTP_PORT}/get?name=john&id=$i")
    if [ "$CODE" = "200" ]; then
        LEGIT_SUCCESS=$((LEGIT_SUCCESS + 1))
        log_info "    Legitimate request $i: HTTP $CODE ✓"
    else
        log_warn "    Legitimate request $i: HTTP $CODE (expected 200)"
    fi
    sleep 0.1
done

if [ $LEGIT_SUCCESS -eq ${RATE_LIMIT_REQUESTS} ]; then
    log_info "  ✓ All ${RATE_LIMIT_REQUESTS} legitimate requests succeeded"
    PASSED=$((PASSED + 1))
else
    fail_test "Not all legitimate requests succeeded ($LEGIT_SUCCESS/${RATE_LIMIT_REQUESTS})"
fi

# ============================================================================
# TC-4: Legitimate traffic flows through all layers
# ============================================================================
log_test "TC-4: Legitimate Traffic Flows Through All Layers"

# Wait for rate limit window to reset
log_info "  Waiting for rate limit window to reset (${RATE_LIMIT_WINDOW_SEC} seconds + buffer)..."
sleep $((RATE_LIMIT_WINDOW_SEC + 2))

log_info "  Sending 10 legitimate requests..."

FLOW_SUCCESS=0
for i in $(seq 1 10); do
    BODY=$(curl -s -H "Host: ${TEST_DOMAIN}" "http://localhost:${HTTP_PORT}/get?test=$i")
    if echo "$BODY" | grep -q "args\|headers\|origin\|url"; then
        FLOW_SUCCESS=$((FLOW_SUCCESS + 1))
        echo "    Request $i: ✓ Success (reached upstream)"
    else
        echo "    Request $i: ✗ Failed (response: ${BODY:0:100}...)"
    fi
    # Space out requests to avoid hitting rate limit
    sleep 0.5
done

log_info "  Total successful: $FLOW_SUCCESS/10"

if [ $FLOW_SUCCESS -ge 5 ]; then
    log_info "  ✓ Legitimate traffic flowing through all layers"
    PASSED=$((PASSED + 1))
else
    fail_test "Too many legitimate requests failed ($FLOW_SUCCESS/10)"
fi

# ============================================================================
# TC-5: Basic latency check
# ============================================================================
log_test "TC-5: Basic Latency Check"

# Wait for rate limit window to reset again
log_info "  Waiting for rate limit window to reset..."
sleep $((RATE_LIMIT_WINDOW_SEC + 2))

# Measure latency for a single request
LATENCY=$(curl -s -o /dev/null -w "%{time_total}" \
    -H "Host: ${TEST_DOMAIN}" \
    "http://localhost:${HTTP_PORT}/get")

log_info "  Single request latency: ${LATENCY}s"

# Convert to milliseconds for comparison (using awk since bc may not be available)
LATENCY_MS=$(echo "$LATENCY" | awk '{printf "%.0f", $1 * 1000}')

if [ "$LATENCY_MS" -lt 5000 ]; then
    log_info "  ✓ Latency ${LATENCY_MS}ms is within acceptable range (<5000ms)"
    PASSED=$((PASSED + 1))
else
    fail_test "Latency ${LATENCY_MS}ms exceeds threshold"
fi

# ============================================================================
# Results Summary
# ============================================================================
echo ""
echo "=============================================="
echo "=== Cerberus Full Integration Test Results ==="
echo "=============================================="
echo ""
echo -e "  ${GREEN}Passed:${NC}  $PASSED"
echo -e "  ${RED}Failed:${NC}  $FAILED"
echo ""

if [ $FAILED -eq 0 ]; then
    echo "=============================================="
    echo "=== ALL CERBERUS INTEGRATION TESTS PASSED ==="
    echo "=============================================="
    echo ""
    exit 0
else
    echo "=============================================="
    echo "=== CERBERUS TESTS FAILED ==="
    echo "=============================================="
    echo ""
    exit 1
fi
