import fs from 'node:fs';

function randomInt(n) {
  return Math.floor(Math.random() * n);
}

function generateBusinessGraph(numUsers = 5000, numGroups = 200, numDocs = 2000) {
  const nodes = [];
  const relations = [];
  const adminKeys = [];
  const newEmployeeKeys = [];
  const testUserKeys = [];
  
  console.log('🏗️  Generating realistic business authorization graph...');
  console.log(`📊 Scale: ${numUsers} users, ${numGroups} groups, ${numDocs} documents`);
  
  // Add organizational groups with realistic structure
  const departments = ['engineering', 'product', 'design', 'marketing', 'sales', 'hr', 'finance', 'legal', 'operations', 'security', 'data', 'research'];
  const levels = ['intern', 'junior', 'mid', 'senior', 'staff', 'principal', 'lead', 'manager', 'director', 'vp'];
  const clearanceLevels = ['public', 'internal', 'confidential', 'restricted', 'secret', 'top_secret'];
  const documentTypes = ['manual', 'specification', 'budget', 'contract', 'policy', 'report', 'plan'];
  const currencies = ['usd', 'eur', 'gbp'];
  
  console.log('📁 Creating organizational structure...');
  
  // Create department groups
  for (let i = 0; i < departments.length; i++) {
    const dept = departments[i];
    nodes.push({ key: `group:${dept}`, type: 'group' });
    
    // Create level-based subgroups within each department
    for (let j = 0; j < levels.length; j++) {
      const level = levels[j];
      const groupKey = `group:${dept}_${level}`;
      nodes.push({ key: groupKey, type: 'group' });
      
      // Create hierarchy: department contains level groups
      relations.push({ src: `group:${dept}`, rel: 'contains', dst: groupKey });
      
      // Create management hierarchy (for computed_userset rules)
      if (j < levels.length - 1) {
        const higherLevel = levels[j + 1];
        const higherGroupKey = `group:${dept}_${higherLevel}`;
        relations.push({ src: higherGroupKey, rel: 'manages', dst: groupKey });
      }
    }
  }
  
  // Add projects and teams
  const projectTypes = ['project', 'team', 'committee', 'guild', 'workgroup'];
  for (let i = 0; i < numGroups - (departments.length * (levels.length + 1)); i++) {
    const type = projectTypes[i % projectTypes.length];
    nodes.push({ key: `group:${type}_${i}`, type: 'group' });
  }
  
  // Add clearance levels, document types, and currencies
  for (const clearance of clearanceLevels) {
    nodes.push({ key: `clearance:${clearance}`, type: 'clearance' });
  }
  for (const docType of documentTypes) {
    nodes.push({ key: `doctype:${docType}`, type: 'document_type' });
  }
  for (const currency of currencies) {
    nodes.push({ key: `currency:${currency}`, type: 'currency' });
  }
  
  // Add budget and cost tracking nodes for financial rules
  for (let i = 0; i < departments.length; i++) {
    const dept = departments[i];
    nodes.push({ key: `budget:${dept}_2024`, type: 'budget' });
    nodes.push({ key: `account:${dept}_operational`, type: 'account' });
  }
  
  console.log('👥 Creating users with realistic attributes...');
  
  // Add users with realistic department/level assignments
  for (let i = 0; i < numUsers; i++) {
    const userKey = `user:${i}`;
    nodes.push({ key: userKey, type: 'user' });
    
    // Assign to department and level with realistic distribution
    const dept = departments[randomInt(departments.length)];
    const level = levels[Math.min(levels.length - 1, Math.floor(Math.abs(gaussianRandom()) * 3) + 2)]; // Bias toward mid-level
    const deptGroupKey = `group:${dept}`;
    const levelGroupKey = `group:${dept}_${level}`;
    
    relations.push({ src: userKey, rel: 'member_of', dst: deptGroupKey });
    relations.push({ src: userKey, rel: 'member_of', dst: levelGroupKey });
    
    // Add user accounts with balances (for relational_comparator rules)
    const accountKey = `account:user_${i}`;
    nodes.push({ key: accountKey, type: 'account' });
    relations.push({ src: userKey, rel: 'owns_account', dst: accountKey });
    
    // Assign random balance (10K - 100K USD)
    const balance = 10000 + randomInt(90000);
    relations.push({ src: accountKey, rel: 'has_balance', dst: 'currency:usd', value: balance });
    
    // Assign clearance level (higher levels get higher clearance)
    const clearanceIndex = Math.min(
      clearanceLevels.length - 1,
      levels.indexOf(level) + randomInt(3)
    );
    const clearance = clearanceLevels[clearanceIndex];
    relations.push({ src: userKey, rel: 'has_clearance', dst: `clearance:${clearance}` });
    
    // Users join multiple cross-functional groups (realistic for large orgs)
    const numGroups = Math.floor(Math.abs(gaussianRandom()) * 3) + 1; // 1-4 groups
    for (let g = 0; g < numGroups; g++) {
      if (Math.random() < 0.4) {
        const projectGroup = `group:project_${randomInt(numGroups - (departments.length * (levels.length + 1)))}`;
        relations.push({ src: userKey, rel: 'member_of', dst: projectGroup });
      }
    }
    
    if (i % 500 === 0) console.log(`  👤 Added ${i} users...`);
  }
  
  // Add system admins with full access
  console.log('🔐 Creating system administrators...');
  for (let i = 0; i < 10; i++) {
    const adminKey = `admin:${i}`;
    nodes.push({ key: adminKey, type: 'admin' });
    adminKeys.push(adminKey);
    
    // Admins have top secret clearance and access to all groups
    relations.push({ src: adminKey, rel: 'has_clearance', dst: 'clearance:top_secret' });
    for (const dept of departments) {
      relations.push({ src: adminKey, rel: 'superadmin', dst: `group:${dept}` });
    }
    
    // Admins have large budgets
    const adminAccount = `account:admin_${i}`;
    nodes.push({ key: adminAccount, type: 'account' });
    relations.push({ src: adminKey, rel: 'owns_account', dst: adminAccount });
    relations.push({ src: adminAccount, rel: 'has_balance', dst: 'currency:usd', value: 1000000 });
  }
  
  // Add new employees (for inference testing - no direct access)
  console.log('🆕 Creating new employees for inference testing...');
  for (let i = 0; i < 50; i++) {
    const newEmpKey = `newbie:${i}`;
    nodes.push({ key: newEmpKey, type: 'user' });
    newEmployeeKeys.push(newEmpKey);
    
    // Give them basic attributes but limited document access
    const dept = departments[randomInt(departments.length)];
    const level = 'intern'; // New employees start as interns
    relations.push({ src: newEmpKey, rel: 'member_of', dst: `group:${dept}` });
    relations.push({ src: newEmpKey, rel: 'member_of', dst: `group:${dept}_${level}` });
    relations.push({ src: newEmpKey, rel: 'has_clearance', dst: 'clearance:internal' });
    
    // Give them small budgets
    const newEmpAccount = `account:newbie_${i}`;
    nodes.push({ key: newEmpAccount, type: 'account' });
    relations.push({ src: newEmpKey, rel: 'owns_account', dst: newEmpAccount });
    relations.push({ src: newEmpAccount, rel: 'has_balance', dst: 'currency:usd', value: 5000 + randomInt(10000) });
  }
  
  // Add test users with specific patterns for inference validation
  console.log('🧪 Creating test users for inference validation...');
  for (let i = 0; i < 100; i++) {
    const testUserKey = `test_user:${i}`;
    nodes.push({ key: testUserKey, type: 'user' });
    testUserKeys.push(testUserKey);
    
    // Create similar patterns to existing users
    const dept = departments[i % departments.length];
    const level = levels[Math.floor(i / departments.length) % levels.length];
    relations.push({ src: testUserKey, rel: 'member_of', dst: `group:${dept}` });
    relations.push({ src: testUserKey, rel: 'member_of', dst: `group:${dept}_${level}` });
    
    const clearanceIndex = Math.min(clearanceLevels.length - 1, levels.indexOf(level) + 1);
    relations.push({ src: testUserKey, rel: 'has_clearance', dst: `clearance:${clearanceLevels[clearanceIndex]}` });
    
    // Give them moderate budgets
    const testAccount = `account:test_${i}`;
    nodes.push({ key: testAccount, type: 'account' });
    relations.push({ src: testUserKey, rel: 'owns_account', dst: testAccount });
    relations.push({ src: testAccount, rel: 'has_balance', dst: 'currency:usd', value: 15000 + randomInt(50000) });
  }
  
  console.log('📄 Creating documents with complex access patterns...');
  
  // Create documents with rich metadata
  const docKeys = [];
  for (let i = 0; i < numDocs; i++) {
    const docKey = `doc:${i}`;
    nodes.push({ key: docKey, type: 'doc' });
    docKeys.push(docKey);
    
    // Classify document with bias toward lower classifications
    const classificationIndex = Math.min(
      clearanceLevels.length - 1,
      Math.floor(Math.abs(gaussianRandom()) * 2) + 1
    );
    const classification = clearanceLevels[classificationIndex];
    relations.push({ src: docKey, rel: 'classified_as', dst: `clearance:${classification}` });
    
    // Assign document type
    const docType = documentTypes[randomInt(documentTypes.length)];
    relations.push({ src: docKey, rel: 'has_type', dst: `doctype:${docType}` });
    
    // Assign document to departments
    const owningDept = departments[randomInt(departments.length)];
    relations.push({ src: docKey, rel: 'owned_by', dst: `group:${owningDept}` });
    
    // Add cost information for budget documents (for relational_comparator rules)
    if (docType === 'budget' || docType === 'contract') {
      const cost = 1000 + randomInt(50000); // $1K - $50K
      relations.push({ src: docKey, rel: 'has_cost', dst: 'currency:usd', value: cost });
    }
  }
  
  // Generate realistic access patterns
  console.log('🔥 Generating complex authorization patterns...');
  
  // Sort documents by "popularity" with more realistic distribution
  const superPopularDocs = docKeys.slice(0, Math.floor(numDocs * 0.05)); // Top 5% - super popular
  const popularDocs = docKeys.slice(Math.floor(numDocs * 0.05), Math.floor(numDocs * 0.25)); // Next 20% - popular
  const commonDocs = docKeys.slice(Math.floor(numDocs * 0.25), Math.floor(numDocs * 0.70)); // Next 45% - common
  const rareDocs = docKeys.slice(Math.floor(numDocs * 0.70)); // Bottom 30% - rare
  
  console.log(`  📊 Super popular: ${superPopularDocs.length}, Popular: ${popularDocs.length}, Common: ${commonDocs.length}, Rare: ${rareDocs.length}`);
  
  // Get all regular users (not admins/newbies/test users)
  const regularUsers = Array.from({ length: numUsers }, (_, i) => `user:${i}`);
  
  // Create GROUP-BASED access patterns (for tuple_to_userset rules)
  console.log('👥 Creating group-based access patterns...');
  for (const dept of departments) {
    const deptGroupKey = `group:${dept}`;
    
    // Department groups get access to documents they own
    const deptDocs = docKeys.filter((_, i) => i % departments.length === departments.indexOf(dept));
    for (const docKey of deptDocs.slice(0, 20)) { // Limit for performance
      relations.push({ src: deptGroupKey, rel: 'can_read', dst: docKey });
    }
  }
  
  // SUPER POPULAR DOCS: Mix of direct and group access
  for (const docKey of superPopularDocs) {
    // 50% direct access, 50% group access
    if (Math.random() < 0.5) {
      // Direct user access
      const numUsersWithAccess = Math.floor(regularUsers.length * 0.4);
      const usersWithAccess = new Set();
      
      for (let i = 0; i < numUsersWithAccess; i++) {
        const userIndex = Math.floor(Math.random() * regularUsers.length);
        const userKey = regularUsers[userIndex];
        if (!usersWithAccess.has(userKey)) {
          usersWithAccess.add(userKey);
          relations.push({ src: userKey, rel: 'can_read', dst: docKey });
        }
      }
    } else {
      // Group-based access (will require tuple_to_userset rule)
      const owningDept = departments[randomInt(departments.length)];
      relations.push({ src: `group:${owningDept}`, rel: 'can_read', dst: docKey });
    }
  }
  
  // POPULAR DOCS: Mostly group access
  for (const docKey of popularDocs) {
    if (Math.random() < 0.8) {
      // Group access
      const owningDept = departments[randomInt(departments.length)];
      relations.push({ src: `group:${owningDept}`, rel: 'can_read', dst: docKey });
    } else {
      // Limited direct access
      const numUsersWithAccess = Math.floor(regularUsers.length * 0.1);
      const usersWithAccess = new Set();
      
      for (let i = 0; i < numUsersWithAccess; i++) {
        const userIndex = Math.floor(Math.random() * regularUsers.length);
        const userKey = regularUsers[userIndex];
        if (!usersWithAccess.has(userKey)) {
          usersWithAccess.add(userKey);
          relations.push({ src: userKey, rel: 'can_read', dst: docKey });
        }
      }
    }
  }
  
  // COMMON and RARE DOCS: Mostly group access
  for (const docKey of [...commonDocs, ...rareDocs]) {
    if (Math.random() < 0.9) {
      // Group access
      const owningDept = departments[randomInt(departments.length)];
      relations.push({ src: `group:${owningDept}`, rel: 'can_read', dst: docKey });
    }
  }
  
  // Give admins direct access to all documents
  console.log('🔐 Granting admin access...');
  for (const adminKey of adminKeys) {
    for (const docKey of docKeys) {
      relations.push({ src: adminKey, rel: 'can_read', dst: docKey });
    }
  }
  
  // Create patterns for new employees and test users
  console.log('🎯 Creating inference-friendly patterns...');
  
  // Give new employees limited direct access
  for (const newEmpKey of newEmployeeKeys) {
    const docsToAccess = Math.floor(superPopularDocs.length * 0.1);
    const accessedDocs = new Set();
    for (let i = 0; i < docsToAccess; i++) {
      const docKey = superPopularDocs[Math.floor(Math.random() * superPopularDocs.length)];
      if (!accessedDocs.has(docKey)) {
        accessedDocs.add(docKey);
        relations.push({ src: newEmpKey, rel: 'can_read', dst: docKey });
      }
    }
  }
  
  // Give test users mixed access patterns
  for (const testUserKey of testUserKeys) {
    const superPopularAccess = Math.floor(superPopularDocs.length * 0.2);
    const accessedDocs = new Set();
    
    for (let i = 0; i < superPopularAccess; i++) {
      const docKey = superPopularDocs[Math.floor(Math.random() * superPopularDocs.length)];
      if (!accessedDocs.has(docKey)) {
        accessedDocs.add(docKey);
        relations.push({ src: testUserKey, rel: 'can_read', dst: docKey });
      }
    }
  }
  
  console.log('✅ Complex graph generation complete!');
  console.log(`📊 Final stats:`);
  console.log(`  • ${nodes.length} nodes`);
  console.log(`  • ${relations.length} relations`);
  console.log(`  • ${adminKeys.length} admins`);
  console.log(`  • ${newEmployeeKeys.length} new employees`);
  console.log(`  • ${testUserKeys.length} test users`);
  console.log(`  • ${departments.length} departments with ${levels.length} levels each`);
  console.log(`  • ${clearanceLevels.length} clearance levels`);
  console.log(`  • ${documentTypes.length} document types`);
  
  return { 
    nodes, 
    relations, 
    adminKeys, 
    newEmployeeKeys, 
    testUserKeys,
    departments,
    levels,
    clearanceLevels,
    documentTypes,
    currencies
  };
}

// Gaussian random number generator for more realistic distributions
function gaussianRandom() {
  let u = 0, v = 0;
  while(u === 0) u = Math.random(); // Converting [0,1) to (0,1)
  while(v === 0) v = Math.random();
  return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
}

// Generate different sized graphs for testing - MUCH BIGGER
const configs = [
  { name: 'small', users: 1000, groups: 100, docs: 500 },
  { name: 'medium', users: 5000, groups: 250, docs: 2000 },
  { name: 'large', users: 10000, groups: 500, docs: 5000 },
  { name: 'huge', users: 25000, groups: 1000, docs: 10000 }
];

const configName = process.argv[2] || 'medium';
const config = configs.find(c => c.name === configName) || configs[1];

console.log(`🎯 Generating ${config.name} graph configuration...`);
const data = generateBusinessGraph(config.users, config.groups, config.docs);

const filename = `big-graph-data-${config.name}.json`;
fs.writeFileSync(filename, JSON.stringify(data, null, 2));
console.log(`💾 Saved to ${filename}`);
console.log(`🚀 Run: node big-graph.test.js ${config.name}`); 