/**
 * One-shot extraction tool for Track D.
 *
 * Reads the legacy server/src/database/init.js, pulls out every
 * CREATE TABLE / CREATE VIRTUAL TABLE statement, categorises each
 * table into one of 7 migration files (core / academics / finance /
 * hr / inventory / settings / misc), and writes 7 Knex migration
 * files under server/src/database/migrations/knex/.
 *
 * Each migration uses knex.schema.raw() with the original CREATE TABLE
 * SQL — this is 100% faithful to the legacy schema, including CHECK
 * constraints, FTS5 virtual tables, triggers, and anything else the
 * Knex DSL can't easily express. Future PRs can convert individual
 * tables to the Knex schema-builder DSL.
 *
 * Run: `node tools/extract-migrations.js` from the server/ directory
 * (or any directory; the script uses absolute paths).
 */

const fs = require('fs');
const path = require('path');

const REPO = path.resolve(__dirname, '..');
const INIT_JS = path.join(REPO, 'server', 'src', 'database', 'init.js');
const MIGRATIONS_DIR = path.join(REPO, 'server', 'src', 'database', 'migrations', 'knex');

// 7-category mapping per the P1 plan §1.4 D.3.
// Tables not matched by any category land in 007_misc.
const CATEGORY_BY_NAME = {
  // 001_core — users, departments, classes, subjects, enrollments, grades,
  // courses, course_enrolments, assignments, submissions, messages, events,
  // calendar_events
  users: '001_core', departments: '001_core', classes: '001_core',
  subjects: '001_core', enrollments: '001_core', grades: '001_core',
  courses: '001_core', course_enrolments: '001_core',
  assignments: '001_core', submissions: '001_core', messages: '001_core',
  events: '001_core', calendar_events: '001_core', student_grades: '001_core',
  // users deps
  password_reset_tokens: '001_core', user_sessions: '001_core',
  user_devices: '001_core', user_roles: '001_core',
  // profile-related
  student_profiles: '001_core', teacher_profiles: '001_core',
  parent_profiles: '001_core', staff_profiles: '001_core',
  // 002_academics — exams, marks, attendance, hostel, transport, library,
  // clubs, crossword, edutainment
  exams: '002_academics', exam_groups: '002_academics',
  exam_group_subjects: '002_academics', exam_results: '002_academics',
  marks: '002_academics', attendance: '002_academics',
  hostels: '002_academics', rooms: '002_academics', room_assignments: '002_academics',
  hostel_blocks: '002_academics', hostel_rooms: '002_academics', hostel_allocations: '002_academics',
  transport_vehicles: '002_academics', transport_routes: '002_academics',
  transport_stops: '002_academics', transport_assignments: '002_academics',
  transport_trips: '002_academics',
  library_books: '002_academics', library_members: '002_academics',
  library_loans: '002_academics', library_categories: '002_academics',
  clubs: '002_academics', club_members: '002_academics', club_attendance: '002_academics',
  crossword_puzzles: '002_academics', crossword_attempts: '002_academics',
  edutainment_games: '002_academics', edutainment_scores: '002_academics',
  // additional exam-related
  question_papers: '002_academics', question_bank: '002_academics',
  test_papers: '002_academics', test_results: '002_academics',
  homework: '002_academics', homework_submissions: '002_academics',
  // 003_finance — fees, payments, invoices, banking, expenses, trips,
  // fee_plans, fee_plan_installments, discounts, student_fee_discounts,
  // bank_accounts, bank_reconciliations, bank_reconciliation_lines
  fees: '003_finance', fee_groups: '003_finance', fee_group_classes: '003_finance',
  fee_installments: '003_finance', fee_transports: '003_finance',
  student_fees: '003_finance', student_transport_fees: '003_finance',
  student_discounts: '003_finance', student_fee_discounts: '003_finance',
  payments: '003_finance', payment_methods: '003_finance',
  invoices: '003_finance', invoice_lines: '003_finance',
  bank_accounts: '003_finance', bank_transactions: '003_finance',
  bank_reconciliations: '003_finance', bank_reconciliation_lines: '003_finance',
  expenses: '003_finance', expense_categories: '003_finance', expense_attachments: '003_finance',
  trips: '003_finance', trip_participants: '003_finance', trip_payments: '003_finance',
  fee_plans: '003_finance', fee_plan_installments: '003_finance',
  discounts: '003_finance', discount_codes: '003_finance',
  chart_of_accounts: '003_finance', finance_categories: '003_finance',
  // 004_hr — staff_records, leave_requests, staff_attendance, payroll_runs,
  // payslips, vacancies, applicants
  staff: '004_hr', staff_records: '004_hr',
  leave_types: '004_hr', leave_requests: '004_hr', leave_balances: '004_hr',
  staff_attendance: '004_hr', staff_attendance_records: '004_hr',
  payroll_runs: '004_hr', payslips: '004_hr', payslip_items: '004_hr',
  payroll_items: '004_hr',
  vacancies: '004_hr', vacancy_applications: '004_hr', applicants: '004_hr',
  hr_employees: '004_hr', hr_departments: '004_hr',
  // 005_inventory — items, item_stock, stock_transactions, item_issues,
  // suppliers, store_locations, item_categories
  items: '005_inventory', item_stock: '005_inventory',
  stock_transactions: '005_inventory', stock_movements: '005_inventory',
  item_issues: '005_inventory', item_issue_items: '005_inventory',
  suppliers: '005_inventory', supplier_contacts: '005_inventory',
  store_locations: '005_inventory', item_categories: '005_inventory',
  inventory_items: '005_inventory', inventory_categories: '005_inventory',
  inventory_locations: '005_inventory', inventory_movements: '005_inventory',
  // 006_settings — settings, system_settings, school_settings, school_settings_history
  settings: '006_settings', system_settings: '006_settings',
  school_settings: '006_settings', school_settings_history: '006_settings',
  app_settings: '006_settings', user_preferences: '006_settings',
  // 007_misc — everything else: notices, attachments, audit_logs,
  // sync_logs, chat_groups, chat_group_members
  notices: '007_misc', notice_targets: '007_misc',
  attachments: '007_misc', attachment_links: '007_misc',
  audit_logs: '007_misc', audit_log: '007_misc',
  sync_logs: '007_misc', sync_log: '007_misc', sync_conflicts: '007_misc',
  chat_groups: '007_misc', chat_group_members: '007_misc', chat_messages: '007_misc',
  // medical
  medical_records: '007_misc', medical_conditions: '007_misc', medical_allergies: '007_misc',
  // file attachments polymorphic
  file_attachments: '007_misc',
  // cross-cutting
  notifications: '007_misc', activity_log: '007_misc',
  // alumni / parent
  alumni: '007_misc', parent_student_links: '007_misc',
  // front office
  visitors: '007_misc', visitor_logs: '007_misc',
  admission_enquiries: '007_misc', complaints: '007_misc',
  phone_call_logs: '007_misc', postal_dispatches: '007_misc',
  postal_receives: '007_misc',
  // hostel-attendance et al
  hostel_visits: '007_misc', transport_maintenance: '007_misc',
  // social
  social_posts: '007_misc', social_comments: '007_misc', social_likes: '007_misc',
  social_shares: '007_misc',
  // e2e test tables
  test_table: '007_misc',
  // any other unrecognized
};

const CATEGORY_TITLES = {
  '001_core': 'Core',
  '002_academics': 'Academics',
  '003_finance': 'Finance',
  '004_hr': 'HR & Payroll',
  '005_inventory': 'Inventory',
  '006_settings': 'Settings',
  '007_misc': 'Misc',
};

/**
 * Extract every CREATE TABLE / CREATE VIRTUAL TABLE statement from the
 * legacy init.js. The init.js is JS, so the SQL is inside backtick
 * template literals. We need to:
 *  1. Find each `db.exec(`CREATE TABLE ... `);` block (and similar)
 *  2. Inside, split on semicolons to get individual statements
 *  3. Keep only the ones that start with CREATE TABLE / CREATE VIRTUAL TABLE
 */
function extractCreateStatements(initJsText) {
  // Find every backtick template literal that contains CREATE TABLE or
  // CREATE VIRTUAL TABLE. Use a simple scan: find backticks, then inside
  // them look for SQL keywords.
  const statements = [];
  const re = /`((?:\\.|[^`\\])*)`/g;
  let m;
  while ((m = re.exec(initJsText)) !== null) {
    const block = m[1];
    // Split on top-level semicolons (newline-terminated). Naive but works
    // because CREATE TABLE bodies don't contain semicolons.
    const parts = block.split(/;\s*\n/);
    for (const raw of parts) {
      // Strip leading SQL comments (lines starting with --) and blank lines
      // before trying to match the CREATE TABLE header.
      const stripped = raw
        .split('\n')
        .filter(line => !/^\s*--/.test(line) && !/^\s*$/.test(line))
        .join('\n')
        .trim();
      if (!stripped) continue;
      // Match CREATE [VIRTUAL] TABLE [IF NOT EXISTS] [schema.]name
      const header = stripped.match(/^CREATE\s+(VIRTUAL\s+)?TABLE\s+(IF\s+NOT\s+EXISTS\s+)?(\[?[\w]+\]?\.)?(\w+)/i);
      if (!header) continue;
      const isVirtual = !!header[1];
      const tableName = header[4];
      // Reconstruct a clean statement with a trailing semicolon.
      const clean = raw.trim() + ';';
      statements.push({ tableName, sql: clean, isVirtual });
    }
  }
  return statements;
}

function main() {
  if (!fs.existsSync(INIT_JS)) {
    console.error(`init.js not found at ${INIT_JS}`);
    process.exit(1);
  }
  if (!fs.existsSync(MIGRATIONS_DIR)) fs.mkdirSync(MIGRATIONS_DIR, { recursive: true });

  const text = fs.readFileSync(INIT_JS, 'utf8');
  const stmts = extractCreateStatements(text);
  console.log(`Extracted ${stmts.length} CREATE TABLE statements from init.js`);

  // Bucket by category
  const buckets = Object.fromEntries(
    Object.keys(CATEGORY_TITLES).map(k => [k, []])
  );
  const unmapped = [];
  for (const s of stmts) {
    const cat = CATEGORY_BY_NAME[s.tableName] || '007_misc';
    if (!CATEGORY_BY_NAME[s.tableName]) {
      unmapped.push(s.tableName);
    }
    buckets[cat].push(s);
  }

  if (unmapped.length) {
    console.log(`Unmapped tables (lumped into 007_misc):`);
    for (const t of [...new Set(unmapped)].sort()) {
      console.log(`  - ${t}`);
    }
  }

  // Emit one Knex migration file per category
  const now = new Date().toISOString();
  for (const [cat, list] of Object.entries(buckets)) {
    if (list.length === 0) continue;
    const file = path.join(MIGRATIONS_DIR, `2026071700000${cat.split('_')[0]}_${cat.split('_')[1]}.js`);
    const title = CATEGORY_TITLES[cat];
    const body = renderMigration(cat, title, list);
    fs.writeFileSync(file, body, 'utf8');
    console.log(`Wrote ${file} (${list.length} tables)`);
  }

  console.log('Done.');
}

function renderMigration(cat, title, list) {
  // We use knex.schema.raw() to execute the original CREATE TABLE SQL
  // verbatim. This preserves CHECK constraints, FTS5 virtual tables,
  // triggers, and anything else the Knex DSL can't express.
  const lines = [];
  lines.push('/**');
  lines.push(' * Auto-generated by tools/extract-migrations.js on ' + new Date().toISOString());
  lines.push(' *');
  lines.push(' * Track D (P1-9) — Knex migration for the ' + title + ' concern.');
  lines.push(' *');
  lines.push(' * Each CREATE TABLE statement is the original SQL from');
  lines.push(' * server/src/database/init.js, executed via knex.schema.raw() to');
  lines.push(' * preserve CHECK constraints, FTS5 virtual tables, triggers, and');
  lines.push(' * other constructs the Knex DSL cannot easily express.');
  lines.push(' *');
  lines.push(' * Future PRs may convert individual tables to knex.schema.createTable()');
  lines.push(' * with the schema-builder DSL.');
  lines.push(' */');
  lines.push('');
  lines.push("exports.up = async function(knex) {");
  for (const s of list) {
    // Sanitize backticks within the SQL — they would break the template
    // literal. The legacy init.js uses single quotes for string literals
    // inside SQL, so we mostly just need to handle newlines.
    const sql = s.sql.replace(/`/g, '\\`');
    lines.push(`  // ${s.tableName}${s.isVirtual ? ' (virtual)' : ''}`);
    lines.push(`  await knex.schema.raw(${JSON.stringify(sql)});`);
    lines.push('');
  }
  lines.push('};');
  lines.push('');
  lines.push('exports.down = async function(knex) {');
  // Drop in reverse order
  for (const s of [...list].reverse()) {
    lines.push(`  await knex.schema.raw('DROP TABLE IF EXISTS ' + ${JSON.stringify(s.tableName)} + ';');`);
  }
  lines.push('};');
  return lines.join('\n') + '\n';
}

main();
