/**
 * Seed Data Generator
 * Generates realistic test data for database seeding
 */

export interface TableSchema {
  name: string;
  fields: FieldSchema[];
}

export interface FieldSchema {
  name: string;
  type: string;
  primaryKey?: boolean;
  unique?: boolean;
  foreignKey?: string; // e.g., "users.id"
  default?: any;
  optional?: boolean;
}

export interface SeedDataOptions {
  recordsPerTable?: number;
  useTestData?: boolean;
}

export class SeedDataGenerator {
  private recordsPerTable: number;

  constructor(options: SeedDataOptions = {}) {
    this.recordsPerTable = options.recordsPerTable || 5;
  }

  /**
   * Generate seed data for all tables, respecting foreign key dependencies
   */
  generateSeedData(tables: TableSchema[]): string {
    // Sort tables by dependency order (tables with no FKs first)
    const sortedTables = this.topologicalSort(tables);

    let seedCode = `import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  console.log('🌱 Seeding database...');

`;

    // Generate seed data for each table
    for (const table of sortedTables) {
      seedCode += this.generateTableSeed(table, tables);
      seedCode += '\n';
    }

    seedCode += `  console.log('✅ Seeding complete!');
}

main()
  .catch((e) => {
    console.error('❌ Seeding failed:', e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
`;

    return seedCode;
  }

  /**
   * Sort tables by foreign key dependencies (topological sort)
   */
  private topologicalSort(tables: TableSchema[]): TableSchema[] {
    const sorted: TableSchema[] = [];
    const visited = new Set<string>();
    const visiting = new Set<string>();

    const visit = (table: TableSchema) => {
      if (visited.has(table.name)) {
        return;
      }

      if (visiting.has(table.name)) {
        // Circular dependency - just continue
        return;
      }

      visiting.add(table.name);

      // Visit dependencies first
      const foreignKeys = this.getForeignKeyTables(table);
      for (const fkTableName of foreignKeys) {
        const fkTable = tables.find(t => t.name === fkTableName);
        if (fkTable && !visited.has(fkTable.name)) {
          visit(fkTable);
        }
      }

      visiting.delete(table.name);
      visited.add(table.name);
      sorted.push(table);
    };

    for (const table of tables) {
      visit(table);
    }

    return sorted;
  }

  /**
   * Get list of tables that this table has foreign keys to
   */
  private getForeignKeyTables(table: TableSchema): string[] {
    const fkTables: string[] = [];

    for (const field of table.fields) {
      if (field.foreignKey) {
        const [tableName] = field.foreignKey.split('.');
        if (!fkTables.includes(tableName)) {
          fkTables.push(tableName);
        }
      }
    }

    return fkTables;
  }

  /**
   * Generate seed code for a single table
   */
  private generateTableSeed(table: TableSchema, allTables: TableSchema[]): string {
    const _modelName = this.toPascalCase(table.name);
    const varName = this.toCamelCase(table.name);

    let code = `  // Seed ${table.name}\n`;
    code += `  console.log('  → Creating ${table.name}...');\n`;
    code += `  const ${varName}Data = [\n`;

    // Generate multiple records
    for (let i = 0; i < this.recordsPerTable; i++) {
      code += '    {\n';

      for (const field of table.fields) {
        // Skip auto-generated fields
        if (this.isAutoGenerated(field)) {
          continue;
        }

        // Skip timestamps (Prisma handles these)
        if (field.name === 'createdAt' || field.name === 'updatedAt' ||
            field.name === 'created_at' || field.name === 'updated_at') {
          continue;
        }

        const value = this.generateFieldValue(field, table, i, allTables);
        if (value !== null) {
          code += `      ${field.name}: ${value},\n`;
        }
      }

      code += '    },\n';
    }

    code += '  ];\n\n';
    code += `  await prisma.${varName}.createMany({\n`;
    code += `    data: ${varName}Data,\n`;
    code += '    skipDuplicates: true,\n';
    code += '  });\n';
    code += `  console.log(\`    ✓ Created \${${varName}Data.length} ${table.name}\`);\n`;

    return code;
  }

  /**
   * Check if a field is auto-generated
   */
  private isAutoGenerated(field: FieldSchema): boolean {
    // Primary keys with UUID or auto-increment
    if (field.primaryKey && (field.type === 'uuid' || field.type === 'integer')) {
      return true;
    }

    return false;
  }

  /**
   * Generate a realistic value for a field
   */
  private generateFieldValue(
    field: FieldSchema,
    _table: TableSchema,
    index: number,
    _allTables: TableSchema[]
  ): string | null {
    // Handle foreign keys - reference the first record of the parent table
    if (field.foreignKey) {
      const [fkTableName] = field.foreignKey.split('.');
      const fkVarName = this.toCamelCase(fkTableName);

      // For audit logs or optional relationships, make some null
      if (field.optional && index % 3 === 0) {
        return 'null';
      }

      // Reference a random record from the parent table
      const refIndex = index % this.recordsPerTable;
      return `${fkVarName}Data[${refIndex}].id`;
    }

    // Handle different field types
    switch (field.type.toLowerCase()) {
      case 'uuid':
        // Generate unique UUIDs (Prisma will auto-generate in createMany)
        return null; // Let Prisma generate

      case 'string':
      case 'text':
        return this.generateStringValue(field.name, index);

      case 'integer':
      case 'number':
      case 'int':
        return this.generateNumberValue(field.name, index);

      case 'float':
      case 'decimal':
        return this.generateDecimalValue(field.name, index);

      case 'boolean':
        return index % 2 === 0 ? 'true' : 'false';

      case 'date':
      case 'datetime':
      case 'timestamp':
        // For timestamps, let Prisma handle defaults
        if (field.name.includes('created') || field.name.includes('updated')) {
          return null;
        }
        return `new Date('2024-01-${String(index + 1).padStart(2, '0')}')`;

      case 'json':
        return JSON.stringify({ example: 'data', index });

      default:
        return '"default value"';
    }
  }

  /**
   * Generate realistic string values based on field name
   */
  private generateStringValue(fieldName: string, index: number): string {
    const name = fieldName.toLowerCase();

    // Email fields
    if (name.includes('email')) {
      return `"user${index + 1}@example.com"`;
    }

    // Password/hash fields
    if (name.includes('password') || name.includes('hash')) {
      // bcrypt hash for "password123"
      return '"$2b$10$YourHashedPasswordHere"';
    }

    // IP address (check before "address" to avoid false match)
    if (name.includes('ip')) {
      return `"192.168.1.${index + 1}"`;
    }

    // Name fields (but not "firstname" or "lastname" specifically)
    if (name.includes('name') && name !== 'firstname' && name !== 'first_name' && name !== 'lastname' && name !== 'last_name') {
      const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank', 'Grace', 'Henry'];
      return `"${names[index % names.length]} ${index > names.length ? 'Smith' : 'Johnson'}"`;
    }

    // First name
    if (name === 'firstname' || name === 'first_name') {
      const names = ['Alice', 'Bob', 'Charlie', 'Diana', 'Eve'];
      return `"${names[index % names.length]}"`;
    }

    // Last name
    if (name === 'lastname' || name === 'last_name') {
      const names = ['Smith', 'Johnson', 'Williams', 'Brown', 'Jones'];
      return `"${names[index % names.length]}"`;
    }

    // Title fields
    if (name.includes('title')) {
      return `"Item ${index + 1}"`;
    }

    // Description fields
    if (name.includes('description') || name.includes('content')) {
      return `"This is a sample description for item ${index + 1}."`;
    }

    // Status fields
    if (name.includes('status')) {
      const statuses = ['active', 'pending', 'completed', 'cancelled'];
      return `"${statuses[index % statuses.length]}"`;
    }

    // Role fields
    if (name.includes('role')) {
      const roles = ['admin', 'user', 'moderator', 'guest'];
      return `"${roles[index % roles.length]}"`;
    }

    // URL fields
    if (name.includes('url') || name.includes('link')) {
      return `"https://example.com/item/${index + 1}"`;
    }

    // Phone fields
    if (name.includes('phone')) {
      return `"555-${String(1000 + index).padStart(4, '0')}"`;
    }

    // Address fields
    if (name.includes('address')) {
      return `"${index + 1} Main Street, City, ST 12345"`;
    }

    // Generic string
    return `"${fieldName} ${index + 1}"`;
  }

  /**
   * Generate realistic number values based on field name
   */
  private generateNumberValue(fieldName: string, index: number): string {
    const name = fieldName.toLowerCase();

    // Age fields
    if (name.includes('age')) {
      return String(20 + (index * 5) % 60);
    }

    // Count fields
    if (name.includes('count') || name.includes('quantity')) {
      return String(index + 1);
    }

    // Generic integer
    return String(index + 1);
  }

  /**
   * Generate realistic decimal values based on field name
   */
  private generateDecimalValue(fieldName: string, index: number): string {
    const name = fieldName.toLowerCase();

    // Price/amount fields
    if (name.includes('price') || name.includes('amount') || name.includes('cost')) {
      return String((index + 1) * 10.99);
    }

    // Percentage fields
    if (name.includes('percent') || name.includes('rate')) {
      return String((index * 10) % 100);
    }

    // Generic decimal
    return String((index + 1) * 1.5);
  }

  /**
   * Convert snake_case or kebab-case to PascalCase
   */
  private toPascalCase(str: string): string {
    return str
      .split(/[-_]/)
      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
      .join('');
  }

  /**
   * Convert snake_case or kebab-case to camelCase
   */
  private toCamelCase(str: string): string {
    const pascal = this.toPascalCase(str);
    return pascal.charAt(0).toLowerCase() + pascal.slice(1);
  }
}
