#!/bin/bash
#
# End-to-end drift detection test
#
# Creates a temporary git repo with specs and source files, then simulates
# code changes that should be caught by openlore drift.
#
# Usage: ./run-drift-test.sh [path-to-openlore-binary]
#
# If no binary path is given, uses npx from the parent project.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
OPENLORE="${1:-node ${SCRIPT_DIR}/../../dist/cli/index.js}"
TMPDIR_BASE="${TMPDIR:-/tmp}"
TEST_DIR="${TMPDIR_BASE}/openlore-drift-e2e-$$"

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

pass_count=0
fail_count=0

pass() {
  echo -e "  ${GREEN}✓${NC} $1"
  pass_count=$((pass_count + 1))
}

fail() {
  echo -e "  ${RED}✗${NC} $1"
  echo -e "    ${RED}$2${NC}"
  fail_count=$((fail_count + 1))
}

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

echo ""
echo "=== openlore drift: End-to-End Test ==="
echo "Test directory: $TEST_DIR"
echo ""

# ============================================================================
# SETUP: Create the brownfield repo with existing specs
# ============================================================================

echo "--- Setup: Creating brownfield repo ---"

mkdir -p "$TEST_DIR"
cd "$TEST_DIR"

# Init git
git init -b main > /dev/null 2>&1
git config user.email "test@example.com"
git config user.name "Test User"

# Create .openlore config
mkdir -p .openlore
cat > .openlore/config.json << 'EOF'
{
  "version": "1.0.0",
  "projectType": "nodejs",
  "openspecPath": "openspec",
  "analysis": { "maxFiles": 500, "includePatterns": [], "excludePatterns": [] },
  "generation": { "model": "claude-sonnet-4-20250514", "domains": "auto" },
  "createdAt": "2025-01-01T00:00:00.000Z",
  "lastRun": "2025-01-15T00:00:00.000Z"
}
EOF

# Create source files
mkdir -p src/auth src/user src/payments src/utils

cat > src/auth/auth-service.ts << 'EOF'
export class AuthService {
  async login(email: string, password: string) {
    // Validate credentials
    const user = await this.findUser(email);
    if (!user || !this.checkPassword(password, user.hashedPassword)) {
      throw new Error('Invalid credentials');
    }
    return this.generateToken(user);
  }

  async register(email: string, password: string) {
    const existing = await this.findUser(email);
    if (existing) throw new Error('User already exists');
    return this.createUser(email, password);
  }

  private findUser(email: string) { return null; }
  private checkPassword(pw: string, hash: string) { return true; }
  private generateToken(user: any) { return 'token'; }
  private createUser(email: string, pw: string) { return {}; }
}
EOF

cat > src/auth/jwt.ts << 'EOF'
export function signToken(payload: object): string {
  return 'signed-token';
}

export function verifyToken(token: string): object | null {
  return { userId: '123' };
}
EOF

cat > src/user/user-model.ts << 'EOF'
export interface User {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
}

export interface UserProfile extends User {
  bio?: string;
  avatarUrl?: string;
}
EOF

cat > src/user/user-service.ts << 'EOF'
import { User } from './user-model';

export class UserService {
  async getUser(id: string): Promise<User | null> {
    return null;
  }

  async updateUser(id: string, data: Partial<User>): Promise<User> {
    return {} as User;
  }

  async deleteUser(id: string): Promise<void> {
    // soft delete
  }
}
EOF

cat > src/payments/payment-handler.ts << 'EOF'
export class PaymentHandler {
  async processPayment(amount: number, currency: string) {
    if (amount <= 0) throw new Error('Invalid amount');
    return { transactionId: 'tx_123', status: 'completed' };
  }

  async refund(transactionId: string) {
    return { status: 'refunded' };
  }
}
EOF

cat > src/utils/format.ts << 'EOF'
export function formatCurrency(amount: number, currency: string): string {
  return `${currency} ${amount.toFixed(2)}`;
}
EOF

cat > package.json << 'EOF'
{
  "name": "drift-test-app",
  "version": "1.0.0",
  "dependencies": {}
}
EOF

# Create OpenSpec specs (the "global spec")
mkdir -p openspec/specs/overview openspec/specs/auth openspec/specs/user openspec/specs/payments openspec/specs/architecture

cat > openspec/config.yaml << 'EOF'
schema: spec-driven
context: |
  A sample Node.js application with authentication, user management, and payments.

  Tech stack: TypeScript, Node.js
  Architecture: Layered (service-oriented)

openlore:
  generatedAt: "2025-01-15T00:00:00.000Z"
  domains:
    - auth
    - user
    - payments
EOF

cat > openspec/specs/overview/spec.md << 'EOF'
# System Overview

> Generated by openlore v1.0.0 on 2025-01-15

## Purpose

A sample application with authentication, user management, and payment processing.

## Domains

- **auth**: Authentication and authorization (login, register, JWT)
- **user**: User management (CRUD, profiles)
- **payments**: Payment processing (charges, refunds)
EOF

cat > openspec/specs/auth/spec.md << 'EOF'
# Auth Specification

> Generated by openlore v1.0.0 on 2025-01-15
> Source files: src/auth/auth-service.ts, src/auth/jwt.ts

## Purpose

Manages authentication and authorization, including user login, registration, and JWT token management.

## Requirements

### Requirement: UserLogin

The system SHALL authenticate users via email and password, returning a JWT token on success.

#### Scenario: ValidLogin
- **GIVEN** a registered user with valid credentials
- **WHEN** the user submits their email and password
- **THEN** the system returns a signed JWT token

#### Scenario: InvalidCredentials
- **GIVEN** invalid credentials
- **WHEN** the user attempts to login
- **THEN** the system throws an 'Invalid credentials' error

### Requirement: UserRegistration

The system SHALL allow new users to register with email and password.

#### Scenario: DuplicateEmail
- **GIVEN** an email that is already registered
- **WHEN** a registration is attempted
- **THEN** the system throws a 'User already exists' error

### Requirement: JWTManagement

The system SHALL sign and verify JWT tokens for session management.

## Technical Notes

- **Implementation**: `src/auth/auth-service.ts`, `src/auth/jwt.ts`
- **Dependencies**: user domain
EOF

cat > openspec/specs/user/spec.md << 'EOF'
# User Specification

> Generated by openlore v1.0.0 on 2025-01-15
> Source files: src/user/user-model.ts, src/user/user-service.ts

## Purpose

Manages user data, profiles, and user lifecycle operations.

## Requirements

### Requirement: UserCRUD

The system SHALL support Create, Read, Update, and Delete operations for users.

#### Scenario: GetUser
- **GIVEN** a valid user ID
- **WHEN** the user is requested
- **THEN** the system returns the user record or null

#### Scenario: DeleteUser
- **GIVEN** a valid user ID
- **WHEN** the user is deleted
- **THEN** the system performs a soft delete

### Requirement: UserProfile

The system SHALL support optional profile fields (bio, avatarUrl) via the UserProfile interface.

## Technical Notes

- **Implementation**: `src/user/user-model.ts`, `src/user/user-service.ts`
- **Dependencies**: none
EOF

cat > openspec/specs/payments/spec.md << 'EOF'
# Payments Specification

> Generated by openlore v1.0.0 on 2025-01-15
> Source files: src/payments/payment-handler.ts

## Purpose

Handles payment processing including charges and refunds.

## Requirements

### Requirement: ProcessPayment

The system SHALL process payments with amount and currency validation.

#### Scenario: ValidPayment
- **GIVEN** a positive amount and valid currency
- **WHEN** a payment is processed
- **THEN** the system returns a transaction ID with status 'completed'

#### Scenario: InvalidAmount
- **GIVEN** an amount of zero or negative
- **WHEN** a payment is attempted
- **THEN** the system throws an 'Invalid amount' error

### Requirement: Refunds

The system SHALL support refunding a previously completed transaction.

## Technical Notes

- **Implementation**: `src/payments/payment-handler.ts`
- **Dependencies**: none
EOF

cat > openspec/specs/architecture/spec.md << 'EOF'
# Architecture Specification

> Generated by openlore v1.0.0 on 2025-01-15

## Purpose

Describes the system structure and architectural patterns.

## Structure

The application follows a layered architecture:
- **Services**: Business logic (auth-service, user-service, payment-handler)
- **Models**: Data structures (user-model)
- **Utils**: Shared helpers (format)

## Requirements

### Requirement: LayeredArchitecture

The system SHALL separate concerns into service, model, and utility layers.

## Technical Notes

- **Implementation**: `src/`
- **Dependencies**: none
EOF

# Commit the baseline
git add -A
git commit -m "Initial commit: baseline app with specs" > /dev/null 2>&1

pass "Brownfield repo created with baseline specs"

# ============================================================================
# TEST 1: No drift on clean baseline
# ============================================================================

echo ""
echo "--- Test 1: No drift on baseline (no changes) ---"

output=$($OPENLORE drift --json 2>&1) || true
total=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['total'])" 2>/dev/null || echo "FAIL")
has_drift=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['hasDrift'])" 2>/dev/null || echo "FAIL")

if [ "$total" = "0" ] && [ "$has_drift" = "False" ]; then
  pass "No drift detected on clean baseline"
else
  fail "Expected no drift on baseline" "Got total=$total, hasDrift=$has_drift"
fi

# ============================================================================
# TEST 2: Gap detection — modify source without updating spec
# ============================================================================

echo ""
echo "--- Test 2: Gap detection — source changed, spec not updated ---"

git checkout -b feature/add-mfa > /dev/null 2>&1

# Significantly modify auth-service without updating the auth spec
cat >> src/auth/auth-service.ts << 'APPEND'

  async verifyMFA(userId: string, code: string): Promise<boolean> {
    // New MFA verification logic
    if (!code || code.length !== 6) {
      throw new Error('Invalid MFA code');
    }
    const storedSecret = await this.getMFASecret(userId);
    return this.validateTOTP(storedSecret, code);
  }

  async enableMFA(userId: string): Promise<{ secret: string; qrCode: string }> {
    const secret = this.generateSecret();
    await this.storeMFASecret(userId, secret);
    return { secret, qrCode: this.generateQRCode(secret) };
  }

  private getMFASecret(userId: string) { return 'secret'; }
  private validateTOTP(secret: string, code: string) { return true; }
  private generateSecret() { return 'newsecret'; }
  private storeMFASecret(userId: string, secret: string) { }
  private generateQRCode(secret: string) { return 'qr://...'; }
APPEND

git add -A
git commit -m "Add MFA support to auth service" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
gaps=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['gaps'])" 2>/dev/null || echo "FAIL")

if [ "$gaps" != "0" ] && [ "$gaps" != "FAIL" ]; then
  pass "Gap detected: auth source changed, spec not updated (gaps=$gaps)"
else
  fail "Expected gap for auth-service.ts change" "Got gaps=$gaps"
fi

# Reset to main for next test
git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 3: Stale spec — delete a source file referenced by spec
# ============================================================================

echo ""
echo "--- Test 3: Stale spec — delete file referenced by spec ---"

git checkout -b feature/remove-jwt > /dev/null 2>&1

# Delete jwt.ts which is referenced in the auth spec
rm src/auth/jwt.ts
git add -A
git commit -m "Remove JWT module (switching to external lib)" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
stale=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['stale'])" 2>/dev/null || echo "FAIL")

if [ "$stale" != "0" ] && [ "$stale" != "FAIL" ]; then
  pass "Stale spec detected: jwt.ts deleted but still referenced (stale=$stale)"
else
  fail "Expected stale issue for deleted jwt.ts" "Got stale=$stale"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 4: Uncovered file — add new file with no spec domain
# ============================================================================

echo ""
echo "--- Test 4: Uncovered file — new file with no spec domain ---"

git checkout -b feature/add-notifications > /dev/null 2>&1

# Add a new domain's files that have no spec coverage
mkdir -p src/notifications
cat > src/notifications/notification-service.ts << 'EOF'
export class NotificationService {
  async sendEmail(to: string, subject: string, body: string) {
    // Send email notification
    return { sent: true };
  }

  async sendPush(userId: string, message: string) {
    // Send push notification
    return { sent: true };
  }

  async sendSMS(phone: string, message: string) {
    // Send SMS notification
    return { sent: true };
  }
}
EOF

git add -A
git commit -m "Add notification service" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
uncovered=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['uncovered'])" 2>/dev/null || echo "FAIL")

if [ "$uncovered" != "0" ] && [ "$uncovered" != "FAIL" ]; then
  pass "Uncovered file detected: notification-service.ts has no spec (uncovered=$uncovered)"
else
  fail "Expected uncovered issue for notification-service.ts" "Got uncovered=$uncovered"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 5: No drift when spec is updated alongside code
# ============================================================================

echo ""
echo "--- Test 5: No drift when code AND spec are both updated ---"

git checkout -b feature/update-payments > /dev/null 2>&1

# Modify payments source AND update the spec
cat >> src/payments/payment-handler.ts << 'APPEND'

  async createSubscription(planId: string, userId: string) {
    return { subscriptionId: 'sub_123', status: 'active' };
  }
APPEND

# Also update the spec
cat >> openspec/specs/payments/spec.md << 'APPEND'

### Requirement: Subscriptions

The system SHALL support creating recurring subscriptions.

#### Scenario: CreateSubscription
- **GIVEN** a valid plan ID and user ID
- **WHEN** a subscription is created
- **THEN** the system returns a subscription ID with status 'active'
APPEND

git add -A
git commit -m "Add subscription support with updated spec" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
gaps=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['gaps'])" 2>/dev/null || echo "FAIL")

if [ "$gaps" = "0" ]; then
  pass "No gap for payments: code and spec both updated"
else
  fail "Expected no gap when spec was also updated" "Got gaps=$gaps"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 6: Domain filter — only show issues for specific domain
# ============================================================================

echo ""
echo "--- Test 6: Domain filter — only check specific domain ---"

git checkout -b feature/multi-domain-changes > /dev/null 2>&1

# Modify both auth AND user without updating either spec
echo "// auth change" >> src/auth/auth-service.ts
echo "// user change" >> src/user/user-service.ts

git add -A
git commit -m "Changes to both auth and user" > /dev/null 2>&1

output=$($OPENLORE drift --json --domains auth 2>&1) || true
all_auth=$(echo "$output" | python3 -c "
import sys, json
data = json.load(sys.stdin)
domains = [i['domain'] for i in data['issues']]
print('OK' if all(d == 'auth' for d in domains) else 'MIXED')
" 2>/dev/null || echo "FAIL")

if [ "$all_auth" = "OK" ]; then
  pass "Domain filter works: only auth issues shown"
else
  fail "Expected only auth domain issues" "Got: $all_auth"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 7: fail-on threshold — error only
# ============================================================================

echo ""
echo "--- Test 7: fail-on threshold ---"

git checkout -b feature/small-change > /dev/null 2>&1

# Small change (1 line) — should be info-level
echo "// minor fix" >> src/auth/jwt.ts

git add -A
git commit -m "Minor fix" > /dev/null 2>&1

# fail-on error: should NOT fail for info-level issues
exit_code_error=0
$OPENLORE drift --fail-on error > /dev/null 2>&1 || exit_code_error=$?

# fail-on info: SHOULD fail for info-level issues
exit_code_info=0
$OPENLORE drift --fail-on info > /dev/null 2>&1 || exit_code_info=$?

if [ "$exit_code_error" = "0" ]; then
  pass "fail-on error: exits 0 for info-level issues"
else
  fail "fail-on error: should exit 0 for info-level issues" "Got exit code $exit_code_error"
fi

if [ "$exit_code_info" != "0" ]; then
  pass "fail-on info: exits non-zero for info-level issues"
else
  fail "fail-on info: should exit non-zero for info-level issues" "Got exit code $exit_code_info"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 8: JSON output is valid JSON
# ============================================================================

echo ""
echo "--- Test 8: JSON output validity ---"

git checkout -b feature/json-test > /dev/null 2>&1
echo "// change" >> src/auth/auth-service.ts
git add -A
git commit -m "Test change" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
valid_json=$(echo "$output" | python3 -c "
import sys, json
try:
  data = json.load(sys.stdin)
  required = ['timestamp', 'baseRef', 'totalChangedFiles', 'specRelevantFiles', 'issues', 'summary', 'hasDrift', 'duration', 'mode']
  if all(k in data for k in required):
    print('VALID')
  else:
    print('MISSING_KEYS')
except:
  print('INVALID')
" 2>/dev/null || echo "FAIL")

if [ "$valid_json" = "VALID" ]; then
  pass "JSON output is valid with all required fields"
else
  fail "JSON output validation failed" "Got: $valid_json"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 9: Hook install/uninstall
# ============================================================================

echo ""
echo "--- Test 9: Pre-commit hook management ---"

$OPENLORE drift --install-hook 2>&1 > /dev/null || true

if [ -f .git/hooks/pre-commit ] && grep -q "openlore-drift-hook" .git/hooks/pre-commit; then
  pass "Pre-commit hook installed with marker"
else
  fail "Pre-commit hook not installed" "Expected .git/hooks/pre-commit with openlore marker"
fi

$OPENLORE drift --uninstall-hook 2>&1 > /dev/null || true

if [ ! -f .git/hooks/pre-commit ] || ! grep -q "openlore-drift-hook" .git/hooks/pre-commit 2>/dev/null; then
  pass "Pre-commit hook uninstalled"
else
  fail "Pre-commit hook not uninstalled" "Marker still present in .git/hooks/pre-commit"
fi

# ============================================================================
# TEST 10: Verbose output includes detailed messages
# ============================================================================

echo ""
echo "--- Test 10: Verbose output ---"

git checkout -b feature/verbose-test > /dev/null 2>&1
cat >> src/auth/auth-service.ts << 'APPEND'
// More significant changes for verbose testing
export function newAuthHelper() { return true; }
export function anotherAuthHelper() { return false; }
APPEND

git add -A
git commit -m "More auth changes" > /dev/null 2>&1

output=$($OPENLORE drift --verbose 2>&1) || true

if echo "$output" | grep -qi "suggestion\|review\|spec"; then
  pass "Verbose output includes detailed information"
else
  # The verbose flag shows suggestions inline, check for basic output
  if echo "$output" | grep -qi "gap\|drift\|auth"; then
    pass "Verbose output includes issue details"
  else
    fail "Verbose output missing details" "Output: $(echo "$output" | head -5)"
  fi
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 11: --max-files limits analyzed files
# ============================================================================

echo ""
echo "--- Test 11: --max-files limits analyzed files ---"

git checkout -b feature/max-files-test > /dev/null 2>&1

# Make changes to 3 different files
echo "// auth change for max-files" >> src/auth/auth-service.ts
echo "// user change for max-files" >> src/user/user-service.ts
echo "// payment change for max-files" >> src/payments/payment-handler.ts

git add -A
git commit -m "Changes to 3 files" > /dev/null 2>&1

output=$($OPENLORE drift --max-files 1 --json 2>&1) || true
total_files=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['totalChangedFiles'])" 2>/dev/null || echo "FAIL")
issue_count=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['total'])" 2>/dev/null || echo "FAIL")

# totalChangedFiles should report ALL changed files (3), but issues should be limited
# because only 1 file was analyzed due to --max-files
if [ "$total_files" = "3" ] && [ "$issue_count" -le 1 ]; then
  pass "--max-files 1 limits to 1 analyzed file (total=3, issues=$issue_count)"
else
  fail "Expected 3 total files but limited analysis" "Got totalChangedFiles=$total_files, issues=$issue_count"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 12: --files filter to specific files
# ============================================================================

echo ""
echo "--- Test 12: --files filter to specific files ---"

git checkout -b feature/files-filter-test > /dev/null 2>&1

echo "// auth change for filter" >> src/auth/auth-service.ts
echo "// user change for filter" >> src/user/user-service.ts

git add -A
git commit -m "Changes to auth and user" > /dev/null 2>&1

output=$($OPENLORE drift --files src/auth/auth-service.ts --json 2>&1) || true
total_files=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['totalChangedFiles'])" 2>/dev/null || echo "FAIL")

if [ "$total_files" = "1" ]; then
  pass "--files filters to specific file"
else
  fail "Expected 1 file with --files filter" "Got totalChangedFiles=$total_files"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 13: Orphaned spec detection
# ============================================================================

echo ""
echo "--- Test 13: Orphaned spec detection ---"

git checkout -b feature/orphaned-test > /dev/null 2>&1

# Delete jwt.ts (referenced in auth spec) and make another change
rm src/auth/jwt.ts
echo "// trigger change" >> src/auth/auth-service.ts

git add -A
git commit -m "Remove jwt.ts" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
orphaned=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['summary']['orphanedSpecs'])" 2>/dev/null || echo "FAIL")

if [ "$orphaned" != "0" ] && [ "$orphaned" != "FAIL" ]; then
  pass "Orphaned spec detected: jwt.ts no longer on disk (orphaned=$orphaned)"
else
  fail "Expected orphaned spec for deleted jwt.ts" "Got orphanedSpecs=$orphaned"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 14: --base explicit ref
# ============================================================================

echo ""
echo "--- Test 14: --base with explicit commit ref ---"

git checkout -b feature/base-test > /dev/null 2>&1

echo "// first change" >> src/auth/auth-service.ts
git add -A
git commit -m "First change" > /dev/null 2>&1

MIDPOINT_SHA=$(git rev-parse HEAD)

echo "// second change" >> src/auth/auth-service.ts
git add -A
git commit -m "Second change" > /dev/null 2>&1

# Compare against midpoint (only second change)
output=$($OPENLORE drift --base "$MIDPOINT_SHA" --json 2>&1) || true
total_files=$(echo "$output" | python3 -c "import sys,json; print(json.load(sys.stdin)['totalChangedFiles'])" 2>/dev/null || echo "FAIL")

if [ "$total_files" = "1" ]; then
  pass "--base with SHA limits comparison to one commit of changes"
else
  fail "Expected 1 changed file from midpoint" "Got totalChangedFiles=$total_files"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 15: CHANGELOG.md / README.md should not cause false positives
# ============================================================================

echo ""
echo "--- Test 15: Markdown files should not trigger drift ---"

git checkout -b feature/md-test > /dev/null 2>&1

echo "# v2.0 Release" >> CHANGELOG.md
echo "# Updated README" >> README.md
echo "# Contributing Guide" > CONTRIBUTING.md
# Also add a real code change to have something to compare
echo "// source change" >> src/auth/auth-service.ts

git add -A
git commit -m "Update docs and auth" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
# CHANGELOG, README, CONTRIBUTING should NOT appear as issues
has_changelog=$(echo "$output" | python3 -c "
import sys, json
data = json.load(sys.stdin)
md_issues = [i for i in data['issues'] if i['filePath'].endswith('.md')]
print('CLEAN' if len(md_issues) == 0 else 'DIRTY')
" 2>/dev/null || echo "FAIL")

if [ "$has_changelog" = "CLEAN" ]; then
  pass "CHANGELOG.md/README.md/CONTRIBUTING.md not flagged as drift"
else
  fail "Markdown files incorrectly flagged as drift" "Got: $has_changelog"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 16: Quiet mode should produce minimal output
# ============================================================================

echo ""
echo "--- Test 16: Quiet mode minimal output ---"

git checkout -b feature/quiet-test > /dev/null 2>&1

cat >> src/auth/auth-service.ts << 'APPEND'
export function newBigFeature() {
  // 30+ lines of significant change
  const data = Array.from({ length: 30 }, (_, i) => `item_${i}`);
  return data.filter(d => d.startsWith('item_1'));
}
APPEND

git add -A
git commit -m "Add big feature" > /dev/null 2>&1

output=$($OPENLORE -q drift 2>&1) || true
line_count=$(echo "$output" | wc -l | tr -d ' ')

# Quiet mode should produce very few lines (just the summary line)
if [ "$line_count" -le 3 ]; then
  pass "Quiet mode produces minimal output ($line_count lines)"
else
  fail "Quiet mode too verbose" "Got $line_count lines: $(echo "$output" | head -3)"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 17: Pre-commit hook blocks real commit
# ============================================================================

echo ""
echo "--- Test 17: Pre-commit hook blocks commit with drift ---"

git checkout -b feature/hook-test > /dev/null 2>&1

# Install the hook
$OPENLORE drift --install-hook 2>&1 > /dev/null || true

# Make a code change without updating spec
echo "// hook test change" >> src/auth/auth-service.ts
git add -A

# Try to commit — hook should block it
hook_commit_result=0
git commit -m "This should be blocked" > /dev/null 2>&1 || hook_commit_result=$?

if [ "$hook_commit_result" -ne 0 ]; then
  pass "Pre-commit hook blocked commit with drift (exit code $hook_commit_result)"
else
  fail "Pre-commit hook did not block commit" "Commit succeeded when it should have failed"
fi

# Verify no commit was made
latest_msg=$(git log -1 --format=%s)
if [ "$latest_msg" != "This should be blocked" ]; then
  pass "No commit was created (last commit: '$latest_msg')"
else
  fail "Commit was created despite hook" "Found commit: $latest_msg"
fi

# Uninstall hook for remaining tests
$OPENLORE drift --uninstall-hook 2>&1 > /dev/null || true

# Commit with --no-verify to clean up
git commit -m "Hook test change" --no-verify > /dev/null 2>&1

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 18: Multiple domains changed simultaneously
# ============================================================================

echo ""
echo "--- Test 18: Multiple domains changed simultaneously ---"

git checkout -b feature/multi-domain > /dev/null 2>&1

echo "// auth change" >> src/auth/auth-service.ts
echo "// user change" >> src/user/user-service.ts
echo "// payment change" >> src/payments/payment-handler.ts

git add -A
git commit -m "Changes across three domains" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
domains_hit=$(echo "$output" | python3 -c "
import sys, json
data = json.load(sys.stdin)
domains = set(i['domain'] for i in data['issues'] if i['domain'])
print(len(domains))
" 2>/dev/null || echo "FAIL")

if [ "$domains_hit" = "3" ]; then
  pass "Detected drift across all 3 domains"
elif [ "$domains_hit" != "FAIL" ] && [ "$domains_hit" -ge 2 ]; then
  pass "Detected drift across $domains_hit domains"
else
  fail "Expected drift across multiple domains" "Got domains=$domains_hit"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 19: Binary/generated files should be ignored
# ============================================================================

echo ""
echo "--- Test 19: Binary and generated files ignored ---"

git checkout -b feature/binary-test > /dev/null 2>&1

# Create "binary" files that should be skipped
echo "fake binary content" > logo.png
echo "fake font" > font.woff2
mkdir -p src/types
echo "// generated" > src/types/index.d.ts

git add -A
git commit -m "Add binary and generated files" > /dev/null 2>&1

output=$($OPENLORE drift --json 2>&1) || true
binary_issues=$(echo "$output" | python3 -c "
import sys, json
data = json.load(sys.stdin)
bad = [i for i in data['issues'] if i['filePath'].endswith(('.png', '.woff2', '.d.ts'))]
print(len(bad))
" 2>/dev/null || echo "FAIL")

if [ "$binary_issues" = "0" ]; then
  pass "Binary and generated files correctly ignored"
else
  fail "Binary/generated files incorrectly flagged" "Got $binary_issues issues for binary files"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# TEST 20: Info-level severity with --fail-on info
# ============================================================================

echo ""
echo "--- Test 20: Info-level issues with --fail-on info ---"

git checkout -b feature/info-test > /dev/null 2>&1

# Small change (1-2 lines on a spec-covered file) should be info-level gap
# jwt.ts is in the auth spec; a 1-line comment = 1 addition = info severity
echo "// minor fix" >> src/auth/jwt.ts

git add -A
git commit -m "Tiny jwt change" > /dev/null 2>&1

# Verify it detects info-level severity
output=$($OPENLORE drift --json 2>&1) || true
severity=$(echo "$output" | python3 -c "
import sys, json
data = json.load(sys.stdin)
sevs = [i['severity'] for i in data['issues'] if i['kind'] == 'gap']
print(sevs[0] if sevs else 'NONE')
" 2>/dev/null || echo "FAIL")

exit_code_info=0
$OPENLORE drift --fail-on info > /dev/null 2>&1 || exit_code_info=$?

exit_code_error=0
$OPENLORE drift --fail-on error > /dev/null 2>&1 || exit_code_error=$?

# info-level gap: fail-on info should exit 1, fail-on error should exit 0
if [ "$severity" = "info" ] && [ "$exit_code_info" -ne 0 ] && [ "$exit_code_error" -eq 0 ]; then
  pass "Info-level gap: --fail-on info exits 1, --fail-on error exits 0 (severity=$severity)"
elif [ "$exit_code_info" -ne 0 ] && [ "$exit_code_error" -eq 0 ]; then
  pass "Threshold works: --fail-on info exits 1, --fail-on error exits 0 (severity=$severity)"
else
  fail "Info-level threshold not working" "severity=$severity, fail-on info=$exit_code_info, fail-on error=$exit_code_error"
fi

git checkout main > /dev/null 2>&1

echo "--- Test 21: --use-llm without API key shows clear error ---"
git checkout feature/add-mfa > /dev/null 2>&1

# Ensure no API keys are set for this test
unset ANTHROPIC_API_KEY 2>/dev/null || true
unset OPENAI_API_KEY 2>/dev/null || true

output=$(eval $OPENLORE drift --use-llm 2>&1) || true
exit_code=$?

if echo "$output" | grep -q "No LLM API key found"; then
  pass "--use-llm without API key shows clear error message"
else
  fail "--use-llm should show API key error" "output: $output"
fi

if echo "$output" | grep -q "ANTHROPIC_API_KEY\|OPENAI_API_KEY"; then
  pass "--use-llm error includes env var names"
else
  fail "--use-llm error should mention env var names" "output: $output"
fi

git checkout main > /dev/null 2>&1

echo "--- Test 22: --use-llm with --json without API key exits non-zero ---"
git checkout feature/add-mfa > /dev/null 2>&1

unset ANTHROPIC_API_KEY 2>/dev/null || true
unset OPENAI_API_KEY 2>/dev/null || true

if eval $OPENLORE drift --use-llm --json > /dev/null 2>&1; then
  fail "--use-llm --json should exit non-zero without API key" "exit_code=0"
else
  pass "--use-llm --json without API key exits non-zero"
fi

git checkout main > /dev/null 2>&1

# ============================================================================
# RESULTS
# ============================================================================

echo ""
echo "=== Results ==="
echo -e "  ${GREEN}Passed: $pass_count${NC}"
if [ "$fail_count" -gt 0 ]; then
  echo -e "  ${RED}Failed: $fail_count${NC}"
else
  echo -e "  Failed: 0"
fi
echo ""

if [ "$fail_count" -gt 0 ]; then
  exit 1
else
  echo -e "${GREEN}All drift detection tests passed!${NC}"
  exit 0
fi
