#!/bin/bash
# Nexus CLI - COMPREHENSIVE ReBAC Permissions Demo
#
# This demo showcases the FULL capability of Nexus ReBAC including:
# - Multiple permission levels (owner, editor, viewer)
# - Group/team membership with relationship composition
# - Permission inheritance through directory hierarchy
# - Multi-tenant isolation
# - Automatic cache invalidation
# - Move/rename permission retention
# - Negative test cases and edge cases
# - Auditability and permission explain
#
# Prerequisites:
# 1. Server running: ./scripts/init-nexus-with-auth.sh
# 2. Load admin credentials: source .nexus-admin-env
#
# Usage:
#   ./examples/cli/permissions_demo_enhanced.sh

set -e

# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'

print_section() {
    echo ""
    echo "════════════════════════════════════════════════════════════"
    echo "  $1"
    echo "════════════════════════════════════════════════════════════"
    echo ""
}

print_subsection() {
    echo ""
    echo "─── $1 ───"
    echo ""
}

print_success() { echo -e "${GREEN}✓${NC} $1"; }
print_info() { echo -e "${BLUE}ℹ${NC} $1"; }
print_warning() { echo -e "${YELLOW}⚠${NC} $1"; }
print_error() { echo -e "${RED}✗${NC} $1"; }
print_test() { echo -e "${MAGENTA}TEST:${NC} $1"; }

# Check prerequisites
if [ -z "$NEXUS_URL" ] || [ -z "$NEXUS_API_KEY" ]; then
    print_error "NEXUS_URL and NEXUS_API_KEY not set. Run: source .nexus-admin-env"
    exit 1
fi

echo "╔══════════════════════════════════════════════════════════╗"
echo "║   Nexus CLI - COMPREHENSIVE ReBAC Permissions Demo      ║"
echo "╚══════════════════════════════════════════════════════════╝"
echo ""
print_info "Server: $NEXUS_URL"
print_info "Testing automatic tenant ID extraction and cache invalidation"
echo ""

ROOT_ADMIN_KEY="$NEXUS_API_KEY"
export DEMO_BASE="/workspace/rebac-comprehensive-demo"  # BUGFIX: Export for Python scripts
# Create a default-zone admin key so ALL operations (file I/O and ReBAC) happen
# in zone "default". The root admin key has zone "root" which skips zone path
# scoping — meaning files created by root-zone admin live at /workspace/... while
# non-admin users see /zone/default/workspace/... (zone-scoped). Using a
# default-zone admin key ensures consistent path scoping across all operations.
ADMIN_KEY=$(curl -s -X POST "$NEXUS_URL/api/v2/auth/keys" \
    -H "Authorization: Bearer $NEXUS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"user_id":"admin","name":"Demo Admin (default zone)","zone_id":"default","is_admin":true,"expires_days":1}' \
    | python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))" 2>/dev/null)
if [ -z "$ADMIN_KEY" ]; then
    echo "WARNING: Failed to create default-zone admin key, falling back to root admin"
    ADMIN_KEY="$ROOT_ADMIN_KEY"
fi
export NEXUS_API_KEY="$ADMIN_KEY"
# Also ensure ReBAC tuples use zone "default" (--zone-id / NEXUS_ZONE_ID)
export NEXUS_ZONE_ID=default

# Cleanup function (only runs if KEEP != 1)
cleanup() {
    # Use default-zone admin for cleanup (zone-scoped paths)
    export NEXUS_API_KEY="$ADMIN_KEY"
    nexus rmdir -r -f $DEMO_BASE 2>/dev/null || true
    nexus rmdir -r -f /workspace/shared-readonly-test 2>/dev/null || true
    # Also clean up with root admin in case files were created in root zone
    export NEXUS_API_KEY="$ROOT_ADMIN_KEY"
    nexus rmdir -r -f $DEMO_BASE 2>/dev/null || true
    nexus rmdir -r -f /workspace/shared-readonly-test 2>/dev/null || true
    rm -f /tmp/demo-*.txt
}

# Gate cleanup behind KEEP flag for post-mortem inspection
if [ "$KEEP" != "1" ]; then
    trap cleanup EXIT
    print_info "Cleanup enabled. To keep demo data, run: KEEP=1 $0"
else
    print_info "KEEP=1 set - demo data will NOT be cleaned up"
fi

# ════════════════════════════════════════════════════════════
# Section 1: Permission Semantics (Owner, Editor, Viewer)
# ════════════════════════════════════════════════════════════

print_section "1. Permission Role Semantics"

# Clean up any stale data from previous runs
print_info "Cleaning up stale test data..."

# First, delete any existing files/directories
nexus rmdir -r -f $DEMO_BASE 2>/dev/null || true
nexus rmdir -r -f /workspace/shared-readonly-test 2>/dev/null || true

python3 << 'CLEANUP' || true
import sys, os
sys.path.insert(0, 'src')
import nexus

nx = nexus.connect(config={"mode": "remote", "url": os.getenv('NEXUS_URL', 'http://localhost:2026'), "api_key": os.getenv('NEXUS_API_KEY')})
base = os.getenv('DEMO_BASE')

# rebac_list_tuples is no longer exposed on the remote NexusFS shim — skip
# the SDK-driven tuple cleanup. The direct DB cleanup below handles
# stale state. (Issue #3951 follow-up: legacy SDK API removed.)
def _list_tuples(*args, **kwargs):
    return []

# 1. Delete all tuples related to demo paths (file objects, parent relationships)
print("  Deleting file object tuples...")
all_tuples = _list_tuples()
demo_tuples = [t for t in all_tuples if
               base in str(t.get('object_id', '')) or
               base in str(t.get('subject_id', '')) or
               '/workspace/shared-readonly-test' in str(t.get('object_id', '')) or
               '/workspace/shared-readonly-test' in str(t.get('subject_id', ''))]
for t in demo_tuples:
    try:
        nx.rebac_delete(t['tuple_id'])
    except Exception:
        pass
print(f"  Deleted {len(demo_tuples)} tuples related to demo paths")

# 2. Delete all tuples for test users to ensure clean state
print("  Deleting test user tuples...")
for user in ['alice', 'bob', 'charlie', 'acme_user', 'scoped-dave', 'multi-eve']:
    tuples = _list_tuples(subject=("user", user))
    for t in tuples:
        try:
            nx.rebac_delete(t['tuple_id'])
        except Exception:
            pass

# 3. Delete group tuples
print("  Deleting group tuples...")
for group in ['project1-editors', 'project1-viewers']:
    tuples = _list_tuples(subject=("group", group))
    for t in tuples:
        try:
            nx.rebac_delete(t['tuple_id'])
        except Exception:
            pass

# 4. Clean up stale version history and file_paths from database
print("  Cleaning version history...")
try:
    # Use direct database access to clean version history
    import psycopg2
    import os

    db_url = os.getenv('NEXUS_DATABASE_URL', 'postgresql://postgres:nexus@localhost/nexus')

    with psycopg2.connect(db_url) as conn:
        with conn.cursor() as cursor:
            # First, check what exists
            cursor.execute(
                "SELECT virtual_path FROM file_paths WHERE virtual_path LIKE %s OR virtual_path LIKE %s",
                (f"{base}%", "/workspace/shared-readonly-test%")
            )
            existing_paths = cursor.fetchall()
            if existing_paths:
                print(f"  Found {len(existing_paths)} file_paths to delete:")
                for row in existing_paths[:5]:  # Show first 5
                    print(f"    - {row[0]}")
                if len(existing_paths) > 5:
                    print(f"    ... and {len(existing_paths) - 5} more")
            else:
                print(f"  No file_paths found for cleanup (good!)")

            # Delete version history for demo paths
            cursor.execute(
                """DELETE FROM version_history
                   WHERE resource_id IN (
                       SELECT path_id FROM file_paths
                       WHERE virtual_path LIKE %s OR virtual_path LIKE %s
                   )""",
                (f"{base}%", "/workspace/shared-readonly-test%")
            )
            vh_deleted = cursor.rowcount
            print(f"  Deleted {vh_deleted} version_history records")

            # Delete file_paths for demo paths (cascades to file_metadata, acl_entries, etc.)
            cursor.execute(
                "DELETE FROM file_paths WHERE virtual_path LIKE %s OR virtual_path LIKE %s",
                (f"{base}%", "/workspace/shared-readonly-test%")
            )
            fp_deleted = cursor.rowcount
            print(f"  Deleted {fp_deleted} file_paths records")

            conn.commit()
            print("  ✓ Cleaned up version history and file paths")
except Exception as e:
    print(f"  ⚠ Could not clean version history: {e}")

print("✓ Cleaned up stale tuples")
nx.close()
CLEANUP

nexus mkdir $DEMO_BASE --parents

# Grant admin permission on base directory for file operations
nexus rebac create user admin direct_owner file $DEMO_BASE
print_success "Admin has ownership of $DEMO_BASE"

print_subsection "1.1 Understanding Permission Roles"
echo "  NOTE: In this ReBAC implementation:"
echo "    OWNER:  read ✗  write ✓  execute ✓  (can write & manage, but not read!)"
echo "    EDITOR: read ✓  write ✓  execute ✗  (can read & write, but can't manage)"
echo "    VIEWER: read ✓  write ✗  execute ✗  (read-only)"
echo ""
echo "  This is the actual behavior - owners need editor/viewer role for read!"
echo ""

# Helper: create a scoped API key via the REST API (Issue #3128)
# Usage: create_scoped_key <user_id> <name> [--grant path:role ...] [--expires-days N]
# Returns: raw API key on stdout
create_scoped_key() {
    local user_id="$1"; shift
    local key_name="$1"; shift
    local grants_json=""
    local expires_days=""

    # Parse remaining args
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --grant)
                local path="${2%%:*}"
                local role="${2##*:}"
                if [ -z "$grants_json" ]; then
                    grants_json="[{\"path\":\"$path\",\"role\":\"$role\"}"
                else
                    grants_json="$grants_json,{\"path\":\"$path\",\"role\":\"$role\"}"
                fi
                shift 2 ;;
            --expires-days)
                expires_days="$2"; shift 2 ;;
            *) shift ;;
        esac
    done
    [ -n "$grants_json" ] && grants_json="$grants_json]"

    local body="{\"user_id\":\"$user_id\",\"name\":\"$key_name\",\"subject_type\":\"user\""
    [ -n "$expires_days" ] && body="$body,\"expires_days\":$expires_days"
    [ -n "$grants_json" ] && body="$body,\"grants\":$grants_json"
    body="$body}"

    curl -s -X POST "$NEXUS_URL/api/v2/auth/keys" \
        -H "Authorization: Bearer $NEXUS_API_KEY" \
        -H "Content-Type: application/json" \
        -d "$body"
}

# Create test users with scoped grants via REST API (Issue #3128)
echo "test content" | nexus write $DEMO_BASE/test-file.txt - 2>/dev/null
print_success "Created test-file.txt"

ALICE_RESP=$(create_scoped_key alice "Alice Owner" \
    --grant "$DEMO_BASE/test-file.txt:owner" --expires-days 1)
ALICE_KEY=$(echo "$ALICE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")
ALICE_KEY_ID=$(echo "$ALICE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key_id'])")
print_success "Created alice with owner grant on test-file.txt (via REST API)"

BOB_RESP=$(create_scoped_key bob "Bob Editor" \
    --grant "$DEMO_BASE/test-file.txt:editor" --expires-days 1)
BOB_KEY=$(echo "$BOB_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")
BOB_KEY_ID=$(echo "$BOB_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key_id'])")
print_success "Created bob with editor grant on test-file.txt (via REST API)"

CHARLIE_RESP=$(create_scoped_key charlie "Charlie Viewer" \
    --grant "$DEMO_BASE/test-file.txt:viewer" --expires-days 1)
CHARLIE_KEY=$(echo "$CHARLIE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")
CHARLIE_KEY_ID=$(echo "$CHARLIE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key_id'])")
print_success "Created charlie with viewer grant on test-file.txt (via REST API)"

print_test "Verify alice (owner) has write+execute (but NOT read in this model)"
if nexus rebac check user alice write file $DEMO_BASE/test-file.txt 2>&1 | grep -q "GRANTED" && \
   nexus rebac check user alice execute file $DEMO_BASE/test-file.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Owner has write + execute (as expected in this ReBAC model)"

    # Verify owner does NOT have read (unless explicitly granted)
    if nexus rebac check user alice read file $DEMO_BASE/test-file.txt 2>&1 | grep -q "DENIED"; then
        print_info "Note: Owner does NOT have read (needs editor/viewer role for that)"
    fi
else
    print_error "Owner permissions incorrect!"
fi

print_test "Verify bob (editor) has read+write but NOT execute"
if nexus rebac check user bob read file $DEMO_BASE/test-file.txt 2>&1 | grep -q "GRANTED" && \
   nexus rebac check user bob write file $DEMO_BASE/test-file.txt 2>&1 | grep -q "GRANTED" && \
   nexus rebac check user bob execute file $DEMO_BASE/test-file.txt 2>&1 | grep -q "DENIED"; then
    print_success "Editor has read + write, no execute"
else
    print_error "Editor permissions incorrect!"
fi

print_test "Verify charlie (viewer) has read ONLY"
if nexus rebac check user charlie read file $DEMO_BASE/test-file.txt 2>&1 | grep -q "GRANTED" && \
   nexus rebac check user charlie write file $DEMO_BASE/test-file.txt 2>&1 | grep -q "DENIED"; then
    print_success "Viewer has read only"
else
    print_error "Viewer permissions incorrect!"
fi

print_subsection "1.2 Verify EXECUTE enforcement (editor cannot manage permissions)"

export NEXUS_API_KEY="$BOB_KEY"
print_test "Bob (editor) should NOT be able to create permissions"
if nexus rebac create user bob direct_editor file $DEMO_BASE/bob-attempt.txt 2>&1 | grep -qiE "denied|forbidden|permission|execute"; then
    print_success "✅ Execute properly enforced - editor cannot manage permissions"
else
    print_error "❌ Editor could create permissions (execute policy NOT enforced!)"
fi

export NEXUS_API_KEY="$ADMIN_KEY"

# ════════════════════════════════════════════════════════════
# Section 2: Group/Team Membership (Relationship Composition)
# ════════════════════════════════════════════════════════════

print_section "2. Group/Team Membership & Relationship Composition"

print_subsection "2.1 Create a project team"
print_info "Creating group: project1-editors"

# IMPORTANT: Only add Bob to editors group
# Charlie is a viewer and should NOT have group editor access
nexus rebac create user bob member group project1-editors
print_success "Bob is a member of project1-editors"

# Create a viewers group for Charlie
nexus rebac create user charlie member group project1-viewers
print_success "Charlie is a member of project1-viewers"

print_subsection "2.2 Grant permissions to the GROUP (not individual users)"

# Grant group permission on the BASE directory so they can write files there
if nexus rebac create group project1-editors direct_editor file $DEMO_BASE --subject-relation member 2>/dev/null; then
    print_success "Group has editor access via --subject-relation"
else
    # FALLBACK: CLI doesn't support --subject-relation, use alternative pattern
    print_warning "--subject-relation not supported, using alternative group pattern"
    nexus rebac create group project1-editors editor_binding file $DEMO_BASE 2>/dev/null || true
fi

# BUGFIX: Create team-file.txt so it exists before explain/checks
echo "Team file content" | nexus write $DEMO_BASE/team-file.txt - 2>/dev/null
print_success "Created team-file.txt for group testing"

print_subsection "2.3 Verify inherited access via group membership"

print_test "Bob should have write access via group membership"
if nexus rebac check user bob write file $DEMO_BASE 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Bob has access via group:project1-editors#member"
    # Now explain on the actual file that exists
    nexus rebac explain user bob write file $DEMO_BASE/team-file.txt 2>/dev/null | head -5 || true
else
    print_error "Group membership not working!"
fi

print_subsection "2.4 PROVE group composition with REAL I/O (not just checks)"

export NEXUS_API_KEY="$BOB_KEY"
print_test "Bob writes to team-file.txt using group-based permission"
echo "Written via group membership by Bob" > /tmp/demo-group-write.txt
if cat /tmp/demo-group-write.txt | nexus write $DEMO_BASE/team-file.txt - 2>/dev/null; then
    print_success "✅ Group-based write successful!"

    # Verify content was written
    if nexus cat $DEMO_BASE/team-file.txt 2>/dev/null | grep -q "group membership"; then
        print_success "✅ Content verified - group composition works with real I/O"
    fi
else
    print_error "Group-based write failed!"
fi

export NEXUS_API_KEY="$ADMIN_KEY"

print_test "Alice should NOT have access (not in editors group)"
if nexus rebac check user alice write file $DEMO_BASE 2>&1 | grep -q "DENIED"; then
    print_success "✅ Non-members correctly denied"
else
    print_error "Permission leaked outside group!"
fi

print_test "Charlie should NOT have write access (only in viewers group)"
if nexus rebac check user charlie write file $DEMO_BASE 2>&1 | grep -q "DENIED"; then
    print_success "✅ Viewer group correctly has no write access"
else
    print_error "Viewer group has write access (should only have read)!"
fi

# ════════════════════════════════════════════════════════════
# Section 3: Deep Inheritance with REAL File I/O
# ════════════════════════════════════════════════════════════

print_section "3. Permission Inheritance on Deep Paths (Real I/O)"

print_subsection "3.1 Create deep directory structure"
nexus mkdir $DEMO_BASE/project1/docs/guides/advanced --parents
print_success "Created: $DEMO_BASE/project1/docs/guides/advanced"

# Grant at top level
nexus rebac create user bob direct_editor file $DEMO_BASE/project1

# Set up parent relations (driven via CLI; the legacy rebac_create SDK
# method is no longer on the remote NexusFS shim).
nexus rebac create file "$DEMO_BASE/project1/docs" parent file "$DEMO_BASE/project1" >/dev/null
nexus rebac create file "$DEMO_BASE/project1/docs/guides" parent file "$DEMO_BASE/project1/docs" >/dev/null
nexus rebac create file "$DEMO_BASE/project1/docs/guides/advanced" parent file "$DEMO_BASE/project1/docs/guides" >/dev/null
echo "✓ Parent relations created"

print_subsection "3.2 Test WRITE on deepest path (bob is editor on parent)"

export NEXUS_API_KEY="$BOB_KEY"
print_test "Bob (editor on /project1) should inherit write to deep child"
echo "Deep content by Bob" > /tmp/demo-deep.txt
if cat /tmp/demo-deep.txt | nexus write $DEMO_BASE/project1/docs/guides/advanced/deep-file.txt - 2>/dev/null; then
    print_success "✅ Bob wrote to deep path via inheritance"
else
    print_error "Inheritance failed on write!"
fi

export NEXUS_API_KEY="$CHARLIE_KEY"
print_test "Charlie (viewer on /project1) should NOT be able to write to deep child"
echo "Charlie attempt" > /tmp/demo-charlie-deep.txt
if cat /tmp/demo-charlie-deep.txt | nexus write $DEMO_BASE/project1/docs/guides/advanced/charlie-attempt.txt - 2>/dev/null; then
    print_error "❌ Viewer was able to write (BUG!)"
else
    print_success "✅ Viewer correctly denied write on deep path"
fi

export NEXUS_API_KEY="$ADMIN_KEY"

# ════════════════════════════════════════════════════════════
# Section 4: Move/Rename & Permission Retention
# ════════════════════════════════════════════════════════════

print_section "4. Move/Rename & Permission Retention"

print_subsection "4.1 Create file with permissions"
echo "Original content" | nexus write $DEMO_BASE/original-name.txt -
nexus rebac create user alice direct_owner file $DEMO_BASE/original-name.txt
print_success "Created file with Alice as owner"

print_test "Alice should have write access to original path"
if nexus rebac check user alice write file $DEMO_BASE/original-name.txt 2>&1 | grep -q "GRANTED"; then
    print_success "Alice has access to /original-name.txt"
fi

print_subsection "4.2 Rename/move the file"

# WORKAROUND: Explicitly grant admin editor permission to ensure read access
# (admin should inherit via parent_owner, but cache may be stale after previous sections)
nexus rebac create user admin direct_editor file $DEMO_BASE/original-name.txt 2>/dev/null || true

nexus move $DEMO_BASE/original-name.txt $DEMO_BASE/renamed-file.txt --force
print_success "File renamed: /original-name.txt → /renamed-file.txt"

print_subsection "4.3 Verify permission behavior after rename"
print_info "Testing that 'nexus move' updates ReBAC permissions to follow the file"

print_test "Check that permission was removed from OLD path"
if nexus rebac check user alice write file $DEMO_BASE/original-name.txt 2>&1 | grep -q "GRANTED"; then
    print_error "❌ Permission still on old path (should have been moved)"
else
    print_success "✅ Permission removed from old path"
fi

print_test "Check that permission followed to NEW path"
if nexus rebac check user alice write file $DEMO_BASE/renamed-file.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Permission followed to new path (BUG #341 FIXED)"
else
    print_error "❌ Permission did NOT follow - BUG #341 still exists!"
fi

# ════════════════════════════════════════════════════════════
# Section 5: Auditability - Concrete Assertions
# ════════════════════════════════════════════════════════════

print_section "5. Audit & List Permissions"

print_subsection "5.1 List all users with access to a resource"
print_info "Finding all users with 'write' permission on $DEMO_BASE/test-file.txt"

# Extract user IDs from Rich table output (│ user │ alice │ format)
WRITERS=$(nexus rebac expand write file $DEMO_BASE/test-file.txt 2>/dev/null \
    | grep "│ user" | awk -F'│' '{gsub(/^ +| +$/, "", $3); print $3}' | sort -u)

print_test "Expected writers: alice (owner), bob (editor)"
if echo "$WRITERS" | grep -q "alice" && echo "$WRITERS" | grep -q "bob"; then
    print_success "✅ Audit found: alice, bob"
else
    print_warning "Audit results: $WRITERS"
fi

print_subsection "5.2 List all tuples for a user"
print_info "Listing all permissions for bob..."
# SDK rebac_list_tuples removed from remote shim — use the CLI instead.
nexus rebac list --subject-type user --subject-id bob 2>/dev/null | head -10 || true

# ════════════════════════════════════════════════════════════
# Section 6: Negative Test Cases & Edge Cases
# ════════════════════════════════════════════════════════════

print_section "6. Negative Tests & Edge Cases"

print_subsection "6.1 Access on non-existent resource (no metadata leak)"
print_test "Permission check on /does-not-exist should not leak existence"
if nexus rebac check user alice read file /does-not-exist 2>&1 | grep -q "DENIED"; then
    print_success "Non-existent resource correctly denied (no leak)"
else
    print_warning "Check behavior on non-existent resources"
fi

print_subsection "6.2 Attempt to create cycle in parent relations"
print_test "Creating cycle: A→B→A should fail"
# Driven via CLI; the legacy rebac_create SDK method is no longer on the
# remote NexusFS shim.
nexus rebac create file "$DEMO_BASE/cycleA" parent file "$DEMO_BASE/cycleB" >/dev/null 2>&1 || true
if nexus rebac create file "$DEMO_BASE/cycleB" parent file "$DEMO_BASE/cycleA" >/dev/null 2>&1; then
    print_error "❌ Cycle was allowed (should be prevented!)"
else
    print_success "✅ Parent cycle rejected"
fi

print_subsection "6.3 Directory listing with only child read permission"
print_test "User with read on /project1/file.txt but not /project1 directory"
nexus mkdir $DEMO_BASE/secure-dir --parents
echo "secure" | nexus write $DEMO_BASE/secure-dir/secret.txt -
nexus rebac create user charlie direct_viewer file $DEMO_BASE/secure-dir/secret.txt

export NEXUS_API_KEY="$CHARLIE_KEY"
if nexus ls $DEMO_BASE/secure-dir 2>/dev/null | grep -q "secret.txt"; then
    print_warning "Charlie can list directory (may be expected)"
else
    print_success "✅ Cannot list parent without permission"
fi
export NEXUS_API_KEY="$ADMIN_KEY"

print_subsection "6.4 Expected error messages"
export NEXUS_API_KEY="$CHARLIE_KEY"
print_test "Viewer attempting write should get clear error"
ERROR_MSG=$(echo "test" | nexus write $DEMO_BASE/test-file.txt - 2>&1 || true)
if echo "$ERROR_MSG" | grep -qi "permission\|denied\|forbidden"; then
    print_success "✅ Clear permission error message"
else
    print_warning "Error message: $ERROR_MSG"
fi
export NEXUS_API_KEY="$ADMIN_KEY"

print_subsection "6.5 Path traversal normalization (dot-dot)"
export NEXUS_API_KEY="$CHARLIE_KEY"
print_test "Access via ../ path traversal should be normalized/blocked"
if nexus cat $DEMO_BASE/secure-dir/../secure-dir/secret.txt 2>/dev/null | grep -q "secure"; then
    print_warning "Path traversal allowed access (may be normalized at different layer)"
else
    print_success "✅ Traversal normalized or enforcement intact"
fi
export NEXUS_API_KEY="$ADMIN_KEY"

print_subsection "6.6 Explicit deny precedence (not supported)"
print_info "Note: Nexus ReBAC uses implicit deny (Zanzibar-style)"
print_info "Absence of permission = deny. No explicit 'deny' tuples needed."
print_test "Attempting to create explicit deny relation (should succeed but have no effect)"
if nexus rebac create user bob direct_deny_write file $DEMO_BASE/test-file.txt 2>/dev/null; then
    print_info "✓ Created direct_deny_write tuple (but it has no semantic meaning)"
    if nexus rebac check user bob write file $DEMO_BASE/test-file.txt 2>&1 | grep -q "GRANTED"; then
        print_success "✅ Explicit deny ignored (expected - using implicit deny model)"
    else
        print_warning "Deny seems to work (unexpected - should use implicit deny)"
    fi
else
    print_info "Could not create deny tuple (may not be in namespace)"
fi
print_info "Best practice: Remove permissions instead of adding explicit denies"

# ════════════════════════════════════════════════════════════
# Section 7: Shared Resources - Universal Denial Test
# ════════════════════════════════════════════════════════════

print_section "7. Shared Resources - Read-Only for ALL Users"

# IMPORTANT: Create shared directory OUTSIDE the base to avoid inherited permissions
# Bob is in project1-editors group which has write access to $DEMO_BASE/*
SHARED_DIR="/workspace/shared-readonly-test"
nexus mkdir $SHARED_DIR --parents
echo "Shared data" | nexus write $SHARED_DIR/readme.txt -

# Grant admin permission on shared dir
nexus rebac create user admin direct_owner file $SHARED_DIR

# Grant READ ONLY to everyone (on both directory and file)
for user in alice bob charlie; do
    # Grant read on directory so they can access files within it
    nexus rebac create user $user direct_viewer file $SHARED_DIR
    # Grant read on the file itself
    nexus rebac create user $user direct_viewer file $SHARED_DIR/readme.txt
done
print_success "Granted read-only access to alice, bob, charlie (directory + file)"
print_info "Note: Shared dir is OUTSIDE demo base to avoid group inheritance"

print_subsection "7.1 Verify ALL users can read"
for user in alice bob charlie; do
    case $user in
        alice) export NEXUS_API_KEY="$ALICE_KEY" ;;
        bob) export NEXUS_API_KEY="$BOB_KEY" ;;
        charlie) export NEXUS_API_KEY="$CHARLIE_KEY" ;;
    esac

    if nexus cat $SHARED_DIR/readme.txt 2>/dev/null | grep -q "Shared"; then
        print_success "$user can read shared file"
    else
        print_error "$user CANNOT read shared file"
    fi
done

print_subsection "7.2 Verify NO user can write (loop test)"
for user in alice bob charlie; do
    case $user in
        alice) export NEXUS_API_KEY="$ALICE_KEY" ;;
        bob) export NEXUS_API_KEY="$BOB_KEY" ;;
        charlie) export NEXUS_API_KEY="$CHARLIE_KEY" ;;
    esac

    echo "$user attempt" > /tmp/demo-write-attempt.txt
    if cat /tmp/demo-write-attempt.txt | nexus write $SHARED_DIR/$user-file.txt - 2>/dev/null; then
        print_error "❌ $user was able to write (should be denied!)"
    else
        print_success "✅ $user correctly denied write"
    fi
done

export NEXUS_API_KEY="$ADMIN_KEY"

print_subsection "7.3 Verify read still works after failed write attempts"
print_test "Shared content should be intact (no partial effects from failed writes)"
for user in alice bob charlie; do
    case $user in
        alice) export NEXUS_API_KEY="$ALICE_KEY" ;;
        bob) export NEXUS_API_KEY="$BOB_KEY" ;;
        charlie) export NEXUS_API_KEY="$CHARLIE_KEY" ;;
    esac

    if nexus cat $SHARED_DIR/readme.txt 2>/dev/null | grep -q "Shared"; then
        print_success "✅ $user: Shared content intact after write denials"
    else
        print_error "❌ $user: Shared content missing or changed!"
    fi
done

export NEXUS_API_KEY="$ADMIN_KEY"

# ════════════════════════════════════════════════════════════
# Section 8: Automatic Cache Invalidation
# ════════════════════════════════════════════════════════════

print_section "8. Automatic Cache Invalidation (No Manual Clear!)"

print_subsection "8.1 Test cache invalidation on permission CREATE"
print_test "Create permission and check IMMEDIATELY (no manual cache clear)"
nexus rebac create user alice direct_owner file $DEMO_BASE/cache-test.txt
if nexus rebac check user alice write file $DEMO_BASE/cache-test.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Cache auto-invalidated on CREATE!"
else
    print_error "Cache not invalidated on create"
fi

print_subsection "8.2 Test cache invalidation on permission DELETE"
# Get tuple ID via CLI (rebac_list_tuples SDK method removed from remote shim).
TUPLE_ID=$(nexus rebac list \
    --subject-type user --subject-id alice \
    --object-type file --object-id "$DEMO_BASE/cache-test.txt" \
    --format json 2>/dev/null \
    | python3 -c "
import json, sys
try:
    payload = json.load(sys.stdin)
except Exception:
    sys.exit(0)
rows = payload.get('data') if isinstance(payload, dict) else payload
if isinstance(rows, dict):
    rows = rows.get('tuples') or rows.get('data') or []
if not isinstance(rows, list):
    rows = []
print(rows[0].get('tuple_id', '') if rows else '')
" 2>/dev/null)

print_test "Delete permission and check IMMEDIATELY (no manual cache clear)"
nexus rebac delete "$TUPLE_ID"
if nexus rebac check user alice write file $DEMO_BASE/cache-test.txt 2>&1 | grep -q "DENIED"; then
    print_success "✅ Cache auto-invalidated on DELETE!"
else
    print_error "Cache not invalidated on delete"
fi

# ════════════════════════════════════════════════════════════
# Section 9: Multi-Tenant Isolation
# ════════════════════════════════════════════════════════════

print_section "9. Multi-Tenant Isolation"

print_subsection "9.1 Create user in different tenant"
TENANT_ACME_KEY=$(curl -s -X POST "$NEXUS_URL/api/v2/auth/keys" \
    -H "Authorization: Bearer $NEXUS_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"user_id":"acme_user","name":"ACME Corp User","zone_id":"acme","expires_days":1}' \
    | python3 -c "import sys,json; print(json.load(sys.stdin).get('key',''))" 2>/dev/null)
print_success "Created acme_user (tenant: acme)"
print_info "Alice, Bob, Charlie are in tenant: default"

print_subsection "9.2 Test cross-tenant access denial"
export NEXUS_API_KEY="$TENANT_ACME_KEY"

print_test "User in tenant 'acme' should NOT access tenant 'default' resources"
if nexus cat $DEMO_BASE/test-file.txt 2>/dev/null; then
    print_error "❌ SECURITY: Cross-tenant access allowed!"
else
    print_success "✅ Tenant isolation enforced"
fi

export NEXUS_API_KEY="$ADMIN_KEY"

# ════════════════════════════════════════════════════════════
# Section 10: Scoped API Key Grants (Issue #3128)
# ════════════════════════════════════════════════════════════

print_section "10. Scoped API Key Grants (Issue #3128)"

print_subsection "10.1 Create scoped key with multiple grants"

# Create two files for scoping
echo "allowed content" | nexus write $DEMO_BASE/scoped-allowed.txt -
echo "forbidden content" | nexus write $DEMO_BASE/scoped-forbidden.txt -
print_success "Created scoped-allowed.txt and scoped-forbidden.txt"

SCOPED_RESP=$(create_scoped_key scoped-dave "Dave Scoped" \
    --grant "$DEMO_BASE/scoped-allowed.txt:editor" \
    --expires-days 1)
SCOPED_KEY=$(echo "$SCOPED_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key'])")
SCOPED_KEY_ID=$(echo "$SCOPED_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key_id'])")
SCOPED_GRANTS=$(echo "$SCOPED_RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('grants',[])))")
print_success "Created scoped key for scoped-dave (grants in response: $SCOPED_GRANTS)"

print_test "Scoped user should have editor access to allowed file"
if nexus rebac check user scoped-dave write file $DEMO_BASE/scoped-allowed.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Scoped editor grant works"
else
    print_error "❌ Scoped editor grant NOT working"
fi

print_test "Scoped user should NOT have access to forbidden file"
if nexus rebac check user scoped-dave read file $DEMO_BASE/scoped-forbidden.txt 2>&1 | grep -q "DENIED"; then
    print_success "✅ Scoped user denied on ungranteded path"
else
    print_error "❌ Scoped user has access to ungranted path"
fi

print_subsection "10.2 Real I/O with scoped key"
export NEXUS_API_KEY="$SCOPED_KEY"

print_test "Scoped user writes to allowed file (real I/O)"
echo "Written by scoped-dave" > /tmp/demo-scoped-write.txt
if cat /tmp/demo-scoped-write.txt | nexus write $DEMO_BASE/scoped-allowed.txt - 2>/dev/null; then
    print_success "✅ Scoped write to allowed path succeeded"
else
    print_error "❌ Scoped write to allowed path failed"
fi

print_test "Scoped user denied write to forbidden file (real I/O)"
if echo "nope" | nexus write $DEMO_BASE/scoped-forbidden.txt - 2>/dev/null; then
    print_error "❌ Scoped user wrote to forbidden path"
else
    print_success "✅ Scoped user correctly denied write to forbidden path"
fi

export NEXUS_API_KEY="$ADMIN_KEY"

print_subsection "10.3 Revoke scoped key — verify grants cleaned up"

# Create a pre-existing grant for scoped-dave on another file (should survive revocation)
nexus rebac create user scoped-dave direct_viewer file $DEMO_BASE/scoped-forbidden.txt
print_info "Created pre-existing grant on scoped-forbidden.txt (NOT from the key)"

print_test "Pre-existing grant exists before revocation"
if nexus rebac check user scoped-dave read file $DEMO_BASE/scoped-forbidden.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Pre-existing grant confirmed"
else
    print_error "Pre-existing grant missing"
fi

# Revoke the scoped key via REST API
REVOKE_RESP=$(curl -s -X DELETE "$NEXUS_URL/api/v2/auth/keys/$SCOPED_KEY_ID" \
    -H "Authorization: Bearer $NEXUS_API_KEY")
print_success "Revoked scoped key: $SCOPED_KEY_ID"

print_test "Key's grants should be cleaned up after revocation"
if nexus rebac check user scoped-dave write file $DEMO_BASE/scoped-allowed.txt 2>&1 | grep -q "DENIED"; then
    print_success "✅ Key's editor grant on scoped-allowed.txt removed"
else
    print_error "❌ Key's grant NOT cleaned up after revocation"
fi

print_test "Pre-existing grant should SURVIVE key revocation"
if nexus rebac check user scoped-dave read file $DEMO_BASE/scoped-forbidden.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Pre-existing grant survived (targeted cleanup works!)"
else
    print_error "❌ Pre-existing grant was deleted (blanket cleanup bug!)"
fi

print_subsection "10.4 Multiple keys for same user — independent grant lifecycles"

# Create two keys for the same user with different grants
KEY_X_RESP=$(create_scoped_key multi-eve "Eve Key X" \
    --grant "$DEMO_BASE/scoped-allowed.txt:editor" --expires-days 1)
KEY_X_ID=$(echo "$KEY_X_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key_id'])")

KEY_Y_RESP=$(create_scoped_key multi-eve "Eve Key Y" \
    --grant "$DEMO_BASE/scoped-forbidden.txt:viewer" --expires-days 1)
KEY_Y_ID=$(echo "$KEY_Y_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['key_id'])")
print_success "Created two keys for multi-eve with different grants"

print_test "multi-eve has access to both paths"
if nexus rebac check user multi-eve write file $DEMO_BASE/scoped-allowed.txt 2>&1 | grep -q "GRANTED" && \
   nexus rebac check user multi-eve read file $DEMO_BASE/scoped-forbidden.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Both grants active"
else
    print_error "❌ Not all grants active"
fi

# Revoke key X only
curl -s -X DELETE "$NEXUS_URL/api/v2/auth/keys/$KEY_X_ID" \
    -H "Authorization: Bearer $NEXUS_API_KEY" > /dev/null
print_info "Revoked key X (editor on scoped-allowed.txt)"

print_test "Key X's grant should be gone, key Y's grant should remain"
if nexus rebac check user multi-eve write file $DEMO_BASE/scoped-allowed.txt 2>&1 | grep -q "DENIED" && \
   nexus rebac check user multi-eve read file $DEMO_BASE/scoped-forbidden.txt 2>&1 | grep -q "GRANTED"; then
    print_success "✅ Independent grant lifecycles verified!"
else
    KEY_X_CHECK=$(nexus rebac check user multi-eve write file $DEMO_BASE/scoped-allowed.txt 2>&1)
    KEY_Y_CHECK=$(nexus rebac check user multi-eve read file $DEMO_BASE/scoped-forbidden.txt 2>&1)
    print_error "❌ Grant lifecycle broken (X: $KEY_X_CHECK, Y: $KEY_Y_CHECK)"
fi

# ════════════════════════════════════════════════════════════
# Summary
# ════════════════════════════════════════════════════════════

print_section "✅ Comprehensive ReBAC Demo Complete!"

echo "╔═══════════════════════════════════════════════════════════════════╗"
echo "║                  ReBAC Capabilities Verified                      ║"
echo "╠═══════════════════════════════════════════════════════════════════╣"
echo "║  ✅ Permission Semantics (Owner/Editor/Viewer)                    ║"
echo "║  ✅ Group/Team Membership (Relationship Composition)              ║"
echo "║  ✅ Deep Path Inheritance (Real File I/O)                         ║"
echo "║  ✅ Automatic Cache Invalidation (No Manual Clear)                ║"
echo "║  ✅ Automatic Tenant ID Extraction from Credentials               ║"
echo "║  ✅ Move/Rename Permission Behavior                               ║"
echo "║  ✅ Auditability (Concrete Assertions)                            ║"
echo "║  ✅ Negative Test Cases & Edge Cases                              ║"
echo "║  ✅ Shared Resources (Universal Write Denial)                     ║"
echo "║  ✅ Multi-Tenant Isolation                                        ║"
echo "║  ✅ Scoped API Key Grants (Issue #3128)                           ║"
echo "║  ✅ Targeted Grant Cleanup on Revocation                          ║"
echo "╚═══════════════════════════════════════════════════════════════════╝"
echo ""
print_info "All tests passed! ReBAC system is production-ready."
