#!/usr/bin/env bash
# Tests for worker CLI
# Run: bash src/worker/tests/test-worker.sh
#
# Requires:
#   - Compiled worker binary at src/worker.out
#   - SQLite library available
#   - review-gate in PATH (optional, for integration tests)

set -uo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKER_BIN="${WORKER_BIN:-$SCRIPT_DIR/../../worker.out}"
REVIEW_GATE="${REVIEW_GATE:-review-gate}"

# Check binary exists
if [[ ! -x "$WORKER_BIN" ]]; then
    echo "ERROR: Worker binary not found at $WORKER_BIN"
    echo "Build with: nix-shell -p nim2 sqlite --run 'nim c -d:release src/worker.nim'"
    exit 1
fi

PASSED=0
FAILED=0

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'

# Isolated test environment
TEST_DIR=$(mktemp -d)
TEST_REPO="$TEST_DIR/repo"
ORIG_DIR="$(pwd)"

# Set up SQLite library and binary paths
SQLITE_OUT=$(nix-build '<nixpkgs>' -A sqlite.out --no-out-link 2>/dev/null)
if [[ -d "${SQLITE_OUT}" ]]; then
    export LD_LIBRARY_PATH="${SQLITE_OUT}/lib:${LD_LIBRARY_PATH:-}"
    export PATH="${SQLITE_OUT}/bin:${PATH}"
fi

# Verify sqlite3 is available
if ! command -v sqlite3 &>/dev/null; then
    echo "ERROR: sqlite3 not found. Run tests with: nix-shell -p sqlite --run 'bash $0'"
    exit 1
fi

cleanup() {
    cd "$ORIG_DIR"
    rm -rf "$TEST_DIR"
}
trap cleanup EXIT

# Initialize test git repo
setup_test_repo() {
    mkdir -p "$TEST_REPO"
    cd "$TEST_REPO"
    git init -q
    git config user.email "test@test.com"
    git config user.name "Test User"
    echo "# Test Repo" > README.md
    git add README.md
    git commit -q -m "Initial commit"

    # Create integration branch (worker uses origin/integration by default)
    git checkout -q -b integration
    git checkout -q master

    # Set up fake origin for fetch operations
    git remote add origin . 2>/dev/null || true

    mkdir -p .worker-state
    mkdir -p .review-state
}

# Helpers
assert_exit_code() {
    local description="$1"
    local expected="$2"
    local actual="$3"

    if [[ "$actual" == "$expected" ]]; then
        echo -e "${GREEN}PASS${NC}: $description"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: $description"
        echo "  Expected exit code: $expected, Actual: $actual"
        ((FAILED++))
    fi
}

assert_output_contains() {
    local description="$1"
    local pattern="$2"
    local output="$3"

    if echo "$output" | grep -q "$pattern"; then
        echo -e "${GREEN}PASS${NC}: $description"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: $description"
        echo "  Pattern not found: '$pattern'"
        echo "  Output was:"
        echo "$output" | sed 's/^/    /' | head -10
        ((FAILED++))
    fi
}

assert_output_not_contains() {
    local description="$1"
    local pattern="$2"
    local output="$3"

    if ! echo "$output" | grep -q "$pattern"; then
        echo -e "${GREEN}PASS${NC}: $description"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: $description"
        echo "  Pattern found but should not be: '$pattern'"
        ((FAILED++))
    fi
}

assert_file_exists() {
    local description="$1"
    local file="$2"

    if [[ -f "$file" ]]; then
        echo -e "${GREEN}PASS${NC}: $description"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: $description"
        echo "  File does not exist: $file"
        ((FAILED++))
    fi
}

assert_dir_exists() {
    local description="$1"
    local dir="$2"

    if [[ -d "$dir" ]]; then
        echo -e "${GREEN}PASS${NC}: $description"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: $description"
        echo "  Directory does not exist: $dir"
        ((FAILED++))
    fi
}

run_worker() {
    "$WORKER_BIN" "$@" 2>&1
}

run_worker_exit() {
    "$WORKER_BIN" "$@" 2>&1
    echo "EXIT:$?"
}

get_worker_state() {
    local task_id="$1"
    sqlite3 .worker-state/bus.db "SELECT state FROM workers WHERE task_id='$task_id'" 2>/dev/null || echo "NOT_FOUND"
}

set_worker_state() {
    local task_id="$1"
    local state="$2"
    sqlite3 .worker-state/bus.db "UPDATE workers SET state='$state' WHERE task_id='$task_id'" 2>/dev/null
}

echo "=== Worker CLI Tests ==="
echo "Worker: $WORKER_BIN"
echo "Test dir: $TEST_DIR"
echo ""

# Set up test repo
setup_test_repo

# --- Spawn Command ---
echo "## Spawn Command"

output=$(run_worker spawn --taskId=test-001 --description="Test task" --fromBranch=master)
exit_code=$?
assert_exit_code "Spawn succeeds" "0" "$exit_code"
assert_output_contains "Spawn shows task ID" "test-001" "$output"
assert_output_contains "Spawn shows branch" "feat/test-001" "$output"
assert_output_contains "Spawn shows state" "ASSIGNED" "$output"
assert_dir_exists "Worktree created" "worktrees/test-001"
assert_file_exists "Context file created" "worktrees/test-001/.worker-ctx.json"

# Check DB state
state=$(get_worker_state "test-001")
if [[ "$state" == "ASSIGNED" ]]; then
    echo -e "${GREEN}PASS${NC}: DB state is ASSIGNED"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: DB state should be ASSIGNED, got: $state"
    ((FAILED++))
fi

# --- Spawn Duplicate ---
echo ""
echo "## Spawn Duplicate (Idempotent)"

output=$(run_worker spawn --taskId=test-001)
exit_code=$?
assert_exit_code "Spawn duplicate returns 0" "0" "$exit_code"
assert_output_contains "Shows already exists" "already exists" "$output"

# --- Spawn Invalid Task ID ---
echo ""
echo "## Spawn Invalid Task ID"

output=$(run_worker spawn --taskId="../etc/passwd" 2>&1)
exit_code=$?
assert_exit_code "Spawn rejects path traversal" "1" "$exit_code"
assert_output_contains "Shows invalid character error" "invalid character" "$output"

output=$(run_worker spawn --taskId="" 2>&1)
exit_code=$?
assert_exit_code "Spawn rejects empty ID" "1" "$exit_code"

# --- Status Command ---
echo ""
echo "## Status Command"

output=$(run_worker status)
exit_code=$?
assert_exit_code "Status succeeds" "0" "$exit_code"
assert_output_contains "Status shows task" "test-001" "$output"
assert_output_contains "Status shows state" "ASSIGNED" "$output"

# JSON output
output=$(run_worker status --json)
assert_output_contains "JSON output has task_id" '"task_id"' "$output"
assert_output_contains "JSON output has state" '"state"' "$output"

# --- Show Command ---
echo ""
echo "## Show Command"

output=$(run_worker show --taskId=test-001)
exit_code=$?
assert_exit_code "Show succeeds" "0" "$exit_code"
assert_output_contains "Show displays task" "Task: test-001" "$output"
assert_output_contains "Show displays description" "Test task" "$output"
assert_output_contains "Show displays state" "State: ASSIGNED" "$output"
assert_output_contains "Show displays branch" "Branch: feat/test-001" "$output"

# --- Show Non-existent ---
echo ""
echo "## Show Non-existent Task"

output=$(run_worker show --taskId=nonexistent 2>&1)
exit_code=$?
assert_exit_code "Show returns 7 for not found" "7" "$exit_code"
assert_output_contains "Shows not found" "not found" "$output"

# --- Start Command ---
echo ""
echo "## Start Command"

output=$(run_worker start --task=test-001)
exit_code=$?
assert_exit_code "Start succeeds" "0" "$exit_code"
assert_output_contains "Start confirms" "Started work" "$output"

state=$(get_worker_state "test-001")
if [[ "$state" == "WORKING" ]]; then
    echo -e "${GREEN}PASS${NC}: State transitioned to WORKING"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: State should be WORKING, got: $state"
    ((FAILED++))
fi

# --- Start Already Working ---
echo ""
echo "## Start Already Working (Idempotent)"

output=$(run_worker start --task=test-001)
exit_code=$?
assert_exit_code "Start when working returns 0" "0" "$exit_code"
assert_output_contains "Shows already working" "Already WORKING" "$output"

# --- Cancel Command ---
echo ""
echo "## Cancel Command"

# Create a new task to cancel
run_worker spawn --taskId=test-cancel --fromBranch=master > /dev/null

output=$(run_worker cancel --taskId=test-cancel --reason="Testing cancel")
exit_code=$?
assert_exit_code "Cancel succeeds" "0" "$exit_code"
assert_output_contains "Cancel confirms" "Cancelled" "$output"

state=$(get_worker_state "test-cancel")
if [[ "$state" == "FAILED" ]]; then
    echo -e "${GREEN}PASS${NC}: State transitioned to FAILED"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: State should be FAILED, got: $state"
    ((FAILED++))
fi

# Cancel with cleanup
run_worker spawn --taskId=test-cleanup --fromBranch=master > /dev/null
output=$(run_worker cancel --taskId=test-cleanup --cleanup)
assert_output_contains "Cleanup confirmed" "Worktree removed" "$output"

if [[ ! -d "worktrees/test-cleanup" ]]; then
    echo -e "${GREEN}PASS${NC}: Worktree removed by cleanup"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: Worktree should be removed"
    ((FAILED++))
fi

# --- Full Lifecycle ---
echo ""
echo "## Full Lifecycle: spawn → start → (simulate done) → approve"

run_worker spawn --taskId=lifecycle-001 --fromBranch=master > /dev/null

# Start
run_worker start --task=lifecycle-001 > /dev/null
state=$(get_worker_state "lifecycle-001")
assert_output_contains "After start: WORKING" "WORKING" "$state"

# Simulate done (manually set to IN_REVIEW since done requires actual git work)
set_worker_state "lifecycle-001" "IN_REVIEW"
state=$(get_worker_state "lifecycle-001")
assert_output_contains "After done: IN_REVIEW" "IN_REVIEW" "$state"

# Approve
output=$(run_worker approve --taskId=lifecycle-001)
assert_output_contains "Approve confirms" "Approved" "$output"
state=$(get_worker_state "lifecycle-001")
if [[ "$state" == "APPROVED" ]]; then
    echo -e "${GREEN}PASS${NC}: After approve: APPROVED"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: State should be APPROVED, got: $state"
    ((FAILED++))
fi

# --- Request Changes ---
echo ""
echo "## Request Changes (Reject)"

run_worker spawn --taskId=test-reject --fromBranch=master > /dev/null
set_worker_state "test-reject" "IN_REVIEW"

output=$(run_worker request-changes --taskId=test-reject --comment="Needs more tests")
exit_code=$?
assert_exit_code "Request-changes succeeds" "0" "$exit_code"
assert_output_contains "Shows changes requested" "Changes requested" "$output"
assert_output_contains "Shows comment" "Needs more tests" "$output"

state=$(get_worker_state "test-reject")
if [[ "$state" == "WORKING" ]]; then
    echo -e "${GREEN}PASS${NC}: State returned to WORKING"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: State should be WORKING, got: $state"
    ((FAILED++))
fi

# --- Invalid State Transitions ---
echo ""
echo "## Invalid State Transitions"

# Try to approve from WORKING state
run_worker spawn --taskId=test-invalid --fromBranch=master > /dev/null
run_worker start --task=test-invalid > /dev/null

output=$(run_worker approve --taskId=test-invalid 2>&1)
exit_code=$?
assert_exit_code "Approve from WORKING returns ExitInvalidTransition" "3" "$exit_code"
assert_output_contains "Approve error message" "Cannot approve" "$output"

# --- Review-Gate Integration ---
echo ""
echo "## Review-Gate Integration"

if command -v "$REVIEW_GATE" &> /dev/null; then
    # Spawn creates review state
    run_worker spawn --taskId=test-review --fromBranch=master > /dev/null

    if [[ -f ".review-state/test-review.json" ]]; then
        echo -e "${GREEN}PASS${NC}: Spawn creates review-gate state"
        ((PASSED++))

        # Check status is pending
        review_status=$(jq -r '.status' .review-state/test-review.json)
        if [[ "$review_status" == "pending" ]]; then
            echo -e "${GREEN}PASS${NC}: Review status is pending"
            ((PASSED++))
        else
            echo -e "${RED}FAIL${NC}: Review status should be pending, got: $review_status"
            ((FAILED++))
        fi
    else
        echo -e "${YELLOW}SKIP${NC}: Review state file not created (review-gate integration may not be active)"
    fi

    # Approve updates review-gate
    set_worker_state "test-review" "IN_REVIEW"
    run_worker approve --taskId=test-review > /dev/null

    if [[ -f ".review-state/test-review.json" ]]; then
        review_status=$(jq -r '.status' .review-state/test-review.json)
        if [[ "$review_status" == "approved" ]]; then
            echo -e "${GREEN}PASS${NC}: Approve updates review-gate to approved"
            ((PASSED++))
        else
            echo -e "${RED}FAIL${NC}: Review status should be approved, got: $review_status"
            ((FAILED++))
        fi
    fi

    # Cancel cleans up review state
    run_worker spawn --taskId=test-review-clean --fromBranch=master > /dev/null
    run_worker cancel --taskId=test-review-clean > /dev/null

    if [[ ! -f ".review-state/test-review-clean.json" ]]; then
        echo -e "${GREEN}PASS${NC}: Cancel cleans up review-gate state"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: Review state should be cleaned up after cancel"
        ((FAILED++))
    fi
else
    echo -e "${YELLOW}SKIP${NC}: review-gate not in PATH, skipping integration tests"
fi

# --- Heartbeat Command ---
echo ""
echo "## Heartbeat Command"

run_worker spawn --taskId=test-heartbeat --fromBranch=master > /dev/null
cd worktrees/test-heartbeat

output=$(run_worker heartbeat --status=working 2>&1)
exit_code=$?
cd "$TEST_REPO"

assert_exit_code "Heartbeat succeeds" "0" "$exit_code"
assert_output_contains "Heartbeat confirms" "Heartbeat" "$output"

# --- Fail Command ---
echo ""
echo "## Fail Command"

run_worker spawn --taskId=test-fail --fromBranch=master > /dev/null
run_worker start --task=test-fail > /dev/null
cd worktrees/test-fail

output=$(run_worker fail --reason="Test failure reason" 2>&1)
exit_code=$?
cd "$TEST_REPO"

assert_exit_code "Fail succeeds" "0" "$exit_code"
assert_output_contains "Fail shows task" "test-fail" "$output"
assert_output_contains "Fail shows reason" "Test failure reason" "$output"

state=$(get_worker_state "test-fail")
if [[ "$state" == "FAILED" ]]; then
    echo -e "${GREEN}PASS${NC}: State transitioned to FAILED"
    ((PASSED++))
else
    echo -e "${RED}FAIL${NC}: State should be FAILED, got: $state"
    ((FAILED++))
fi

# --- Status Filtering ---
echo ""
echo "## Status Filtering"

# Create workers in different states
run_worker spawn --taskId=filter-assigned --fromBranch=master > /dev/null
run_worker spawn --taskId=filter-working --fromBranch=master > /dev/null
run_worker start --task=filter-working > /dev/null

output=$(run_worker status --state=ASSIGNED)
assert_output_contains "Filter by ASSIGNED shows assigned worker" "filter-assigned" "$output"
assert_output_not_contains "Filter by ASSIGNED hides working worker" "filter-working" "$output"

output=$(run_worker status --state=WORKING)
assert_output_contains "Filter by WORKING shows working worker" "filter-working" "$output"
assert_output_not_contains "Filter by WORKING hides assigned worker" "filter-assigned" "$output"

# --- Context File ---
echo ""
echo "## Context File Validation"

# Check context file content
if [[ -f "worktrees/test-001/.worker-ctx.json" ]]; then
    ctx_task=$(jq -r '.task_id' worktrees/test-001/.worker-ctx.json)
    ctx_branch=$(jq -r '.branch' worktrees/test-001/.worker-ctx.json)

    if [[ "$ctx_task" == "test-001" ]]; then
        echo -e "${GREEN}PASS${NC}: Context file has correct task_id"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: Context task_id should be test-001, got: $ctx_task"
        ((FAILED++))
    fi

    if [[ "$ctx_branch" == "feat/test-001" ]]; then
        echo -e "${GREEN}PASS${NC}: Context file has correct branch"
        ((PASSED++))
    else
        echo -e "${RED}FAIL${NC}: Context branch should be feat/test-001, got: $ctx_branch"
        ((FAILED++))
    fi
else
    echo -e "${RED}FAIL${NC}: Context file missing"
    ((FAILED++))
    ((FAILED++))
fi

# --- Summary ---
echo ""
echo "=== Summary ==="
echo -e "Passed: ${GREEN}$PASSED${NC}"
echo -e "Failed: ${RED}$FAILED${NC}"

if [[ $FAILED -gt 0 ]]; then
    exit 1
fi

echo ""
echo -e "${GREEN}All tests passed!${NC}"
