/**
 * Bug Condition Exploration Tests
 * 
 * These tests encode the EXPECTED (correct) behavior for each bug condition.
 * On UNFIXED code, they are expected to FAIL — failure confirms the bugs exist.
 * After fixes are applied, these tests should PASS.
 * 
 * **Validates: Requirements 1.1–1.21 (Bug Conditions)**
 */
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const ROOT = path.resolve(__dirname, '..');

describe('Bug Condition Exploration — Security', () => {

  // 1a. Hardcoded secrets in config/db.js
  it('1a. config/db.js should NOT contain hardcoded mongodb+srv:// connection strings', () => {
    const dbSource = fs.readFileSync(path.join(ROOT, 'config', 'db.js'), 'utf-8');
    expect(dbSource).not.toMatch(/mongodb\+srv:\/\//);
  });

  // 1b. SetEnv secrets in .htaccess
  it('1b. .htaccess should NOT contain SetEnv directives for secrets', () => {
    const htaccess = fs.readFileSync(path.join(ROOT, '.htaccess'), 'utf-8');
    expect(htaccess).not.toMatch(/SetEnv\s+JWT_SECRET/);
    expect(htaccess).not.toMatch(/SetEnv\s+MONGO_URI/);
    expect(htaccess).not.toMatch(/SetEnv\s+EMAIL_PASS/);
    expect(htaccess).not.toMatch(/SetEnv\s+WHATSAPP_API_KEY/);
  });

  // 1c. Firebase service account in git
  it('1c. No *firebase-adminsdk*.json file should exist in repo tree', () => {
    const files = findFilesRecursive(ROOT, /firebase-adminsdk.*\.json$/);
    expect(files).toHaveLength(0);
  });

  // 1d. JWT secret strength — no validation exists on unfixed code
  it('1d. Application should reject weak JWT_SECRET at startup', () => {
    // Check if there's any JWT_SECRET validation logic in index.js
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    const hasJwtValidation = /JWT_SECRET/.test(indexSource) && 
      (/process\.exit/.test(indexSource) && /JWT_SECRET/.test(indexSource) &&
       indexSource.includes('length') || indexSource.includes('.length'));
    
    // The app should have JWT_SECRET strength validation that rejects weak secrets
    expect(hasJwtValidation).toBe(true);
  });

  // 1e. Socket.io CORS open
  it('1e. Socket.io config should NOT use origin: "*"', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    // Find the socketIo/io initialization section
    const socketSection = indexSource.match(/socketIo\(server,\s*\{[\s\S]*?\}\s*\)/);
    expect(socketSection).not.toBeNull();
    if (socketSection) {
      expect(socketSection[0]).not.toMatch(/origin:\s*["']\*["']/);
    }
  });

  // 1f. Chat no authorization — send_message handler doesn't check receiver is party to order
  it('1f. send_message handler should verify receiver is party to the order', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    // Extract the send_message handler
    const sendMsgMatch = indexSource.match(/socket\.on\(['"]send_message['"][\s\S]*?(?=socket\.on\()/);
    expect(sendMsgMatch).not.toBeNull();
    if (sendMsgMatch) {
      const handler = sendMsgMatch[0];
      // Should check that receiver is order.client or order.captain
      const checksReceiverParty = handler.includes('order.client') && handler.includes('order.captain') &&
        (handler.includes('receiver') || handler.includes('receiverId'));
      expect(checksReceiverParty).toBe(true);
    }
  });

  // 1g. Location spoofing — update_location doesn't check data.userId === socket.userId
  it('1g. update_location handler should check data.userId === socket.userId', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    // Extract the update_location handler
    const locationMatch = indexSource.match(/socket\.on\(['"]update_location['"][\s\S]*?(?=socket\.on\(['"]disconnect)/);
    expect(locationMatch).not.toBeNull();
    if (locationMatch) {
      const handler = locationMatch[0];
      // Should compare data.userId with socket.userId
      const checksIdentity = handler.includes('socket.userId') && 
        (handler.includes('data.userId') || handler.includes('userId')) &&
        (handler.match(/data\.userId\s*(!==|===)\s*socket\.userId/) ||
         handler.match(/socket\.userId\s*(!==|===)\s*data\.userId/) ||
         handler.match(/String\(data\.userId\)\s*(!==|===)\s*socket\.userId/));
      expect(checksIdentity).toBeTruthy();
    }
  });

  // 1h. Uploads wildcard CORS
  it('1h. /uploads route should NOT use Access-Control-Allow-Origin: *', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    // Find the /uploads middleware section
    const uploadsMatch = indexSource.match(/app\.use\(['"]\/uploads['"][\s\S]*?express\.static/);
    expect(uploadsMatch).not.toBeNull();
    if (uploadsMatch) {
      const uploadsSection = uploadsMatch[0];
      expect(uploadsSection).not.toMatch(/['"]Access-Control-Allow-Origin['"]\s*,\s*['"]\*['"]/);
    }
  });
});

describe('Bug Condition Exploration — Integration', () => {

  // 1i. Dead code config/db.js — not imported in index.js
  it('1i. config/db.js should be imported and used in index.js', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    const importsConfigDb = indexSource.match(/require\(['"]\.\/config\/db['"]\)/) ||
                            indexSource.match(/require\(['"]\.\.\/config\/db['"]\)/);
    expect(importsConfigDb).not.toBeNull();
  });
});

describe('Bug Condition Exploration — User Model', () => {

  // 1j. Email blocks OTP registration
  it('1j. User model email field should NOT be required', () => {
    const userSource = fs.readFileSync(path.join(ROOT, 'models', 'User.js'), 'utf-8');
    // Check if email field has required: true
    const emailFieldMatch = userSource.match(/email:\s*\{[^}]*\}/);
    expect(emailFieldMatch).not.toBeNull();
    if (emailFieldMatch) {
      expect(emailFieldMatch[0]).not.toMatch(/required:\s*true/);
    }
  });

  // 1k. Captain auto-approved
  it('1k. approvalStatus default should be "pending", not "approved"', () => {
    const userSource = fs.readFileSync(path.join(ROOT, 'models', 'User.js'), 'utf-8');
    // Check the approvalStatus field default
    const approvalMatch = userSource.match(/approvalStatus:\s*\{[^}]*\}/);
    expect(approvalMatch).not.toBeNull();
    if (approvalMatch) {
      expect(approvalMatch[0]).not.toMatch(/default:\s*['"]approved['"]/);
    }
  });
});

describe('Bug Condition Exploration — Reliability', () => {

  // 1l. Scheduler before DB
  it('1l. Scheduler should NOT execute cron before DB connection is ready', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    const schedulerSource = fs.readFileSync(path.join(ROOT, 'scheduler.js'), 'utf-8');
    
    // Check if startScheduler is called after mongoose connection is confirmed
    // In unfixed code, startScheduler is called directly without waiting for DB
    const schedulerCallIndex = indexSource.indexOf('startScheduler(');
    const mongooseConnectIndex = indexSource.indexOf('mongoose.connect(');
    
    // The scheduler should either:
    // 1. Be called inside a .then() after mongoose.connect
    // 2. Be called after await connectDB()
    // 3. Internally wait for mongoose.connection.readyState === 1
    
    // Check if scheduler waits for connection internally
    const schedulerWaitsForDb = schedulerSource.includes('readyState') ||
      schedulerSource.includes("once('connected'") ||
      schedulerSource.includes('once("connected"');
    
    // Check if index.js calls scheduler inside .then() of mongoose.connect
    const calledAfterConnect = indexSource.match(/mongoose\.connect[\s\S]*?\.then\([\s\S]*?startScheduler/);
    
    // Check if index.js uses await connectDB() before startScheduler
    const awaitBeforeScheduler = indexSource.match(/await\s+connectDB\(\)[\s\S]*?startScheduler/);
    
    expect(schedulerWaitsForDb || calledAfterConnect || awaitBeforeScheduler).toBeTruthy();
  });

  // 1m. Settings unwaited deleteMany
  it('1m. Settings.getSettings() should await deleteMany', () => {
    const settingsSource = fs.readFileSync(path.join(ROOT, 'models', 'Settings.js'), 'utf-8');
    // Find the getSettings method and check if deleteMany is awaited
    const getSettingsMatch = settingsSource.match(/getSettings\s*=\s*async\s*function[\s\S]*?(?=settingsSchema\.statics\.|module\.exports)/);
    expect(getSettingsMatch).not.toBeNull();
    if (getSettingsMatch) {
      const method = getSettingsMatch[0];
      // Check if deleteMany is preceded by await
      const hasAwaitedDelete = method.match(/await\s+this\.deleteMany/) || 
                               method.match(/await\s+this\.model[\s\S]*?deleteMany/);
      // The fire-and-forget pattern (no await) should NOT be present
      const hasFireAndForget = method.match(/this\.deleteMany\([^)]*\)\s*\n\s*\.then/) ||
                               method.match(/this\.deleteMany\([^)]*\)\s*$/m);
      expect(hasAwaitedDelete).not.toBeNull();
    }
  });

  // 1n. Loose auth rate limit
  it('1n. Auth routes should have a specific (strict) rate limiter', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    // Check if there's a specific rate limiter for auth routes
    const hasAuthLimiter = indexSource.match(/authLimiter|auth.*[Ll]imit/) ||
      (indexSource.match(/rateLimit/) && indexSource.match(/\/api\/auth.*limiter|auth.*rateLimit/));
    
    // Also check if auth routes have their own limiter applied
    const authRoutesWithLimiter = indexSource.match(/['"]\/auth['"].*limiter|['"]auth['"].*limiter/) ||
      indexSource.match(/authLimiter.*['"]\/auth['"]/);
    
    expect(hasAuthLimiter || authRoutesWithLimiter).toBeTruthy();
  });

  // 1o. Huge JSON limit
  it('1o. express.json limit should NOT be >= 50mb', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    const jsonLimitMatch = indexSource.match(/express\.json\(\s*\{[^}]*limit:\s*['"]([^'"]+)['"]/);
    expect(jsonLimitMatch).not.toBeNull();
    if (jsonLimitMatch) {
      const limit = jsonLimitMatch[1];
      // Should not be 50mb or higher
      expect(limit).not.toMatch(/^(50|[5-9]\d|[1-9]\d{2,})mb$/i);
    }
  });
});

describe('Bug Condition Exploration — Cleanup', () => {

  // 1p. No structured logger
  it('1p. Application should use a structured logger (pino/winston), not raw console.log', () => {
    const indexSource = fs.readFileSync(path.join(ROOT, 'index.js'), 'utf-8');
    // Check if pino or winston is imported
    const hasStructuredLogger = indexSource.match(/require\(['"]pino['"]\)/) ||
      indexSource.match(/require\(['"]winston['"]\)/) ||
      indexSource.match(/require\(['"]\.\/utils\/logger['"]\)/) ||
      indexSource.match(/require\(['"]\.\.\/utils\/logger['"]\)/);
    expect(hasStructuredLogger).not.toBeNull();
  });

  // 1q. No .gitignore
  it('1q. .gitignore should exist at repo root with required exclusions', () => {
    const gitignorePath = path.join(ROOT, '.gitignore');
    expect(fs.existsSync(gitignorePath)).toBe(true);
    
    if (fs.existsSync(gitignorePath)) {
      const content = fs.readFileSync(gitignorePath, 'utf-8');
      expect(content).toMatch(/node_modules/);
      expect(content).toMatch(/firebase-adminsdk/);
      expect(content).toMatch(/\.env/);
    }
  });

  // 1r. Invalid mongoose version
  it('1r. package.json mongoose dependency should be a published version (not ^9.x)', () => {
    const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
    const mongooseVersion = pkg.dependencies && pkg.dependencies.mongoose;
    expect(mongooseVersion).toBeDefined();
    // Should NOT start with ^9 (no published 9.x exists)
    expect(mongooseVersion).not.toMatch(/^\^9\./);
  });

  // 1s. No test runner
  it('1s. package.json test script should NOT end with "exit 1"', () => {
    const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
    const testScript = pkg.scripts && pkg.scripts.test;
    expect(testScript).toBeDefined();
    expect(testScript).not.toMatch(/exit\s+1\s*$/);
  });
});

// Helper: recursively find files matching a pattern
function findFilesRecursive(dir, pattern, results = []) {
  // Skip node_modules, .git, android/app/build directories
  const skipDirs = ['node_modules', '.git', 'build', '.gradle'];
  
  let entries;
  try {
    entries = fs.readdirSync(dir, { withFileTypes: true });
  } catch {
    return results;
  }
  
  for (const entry of entries) {
    if (skipDirs.includes(entry.name)) continue;
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      findFilesRecursive(fullPath, pattern, results);
    } else if (pattern.test(entry.name)) {
      results.push(fullPath);
    }
  }
  return results;
}
