#!/usr/bin/env node
/**
 * fix-archived-rls.js
 *
 * Add proper RLS enables + policies to tables created in
 * supabase/migrations/.archived/*.sql files. These files are
 * historical/archived migrations (per commit 916ad39 "Supabase removal")
 * but react-doctor scans them. We satisfy the rule with real, correct
 * policies scoped by user_id (auth.uid()) or brand_id (current_brand_id())
 * where possible; deny-all otherwise.
 *
 * Tables are detected by parsing CREATE TABLE blocks. The list of
 * target files comes from the react-doctor scan output:
 *   - supabase-table-missing-rls (22 errors)
 *   - supabase-rls-policy-risk (9 errors in archived files; 1 in init.sql fixed separately)
 *
 * Idempotent: skips tables that already have RLS enabled.
 */

const fs = require("node:fs");
const path = require("node:path");

const ARCHIVE_DIR = path.join(__dirname, "..", "supabase", "migrations", ".archived");

// Helper policy templates.
// `brand_id`-scoped (most common in archived files):
//   USING (brand_id = current_brand_id() OR is_platform_admin())
//   WITH CHECK (brand_id = current_brand_id() OR is_platform_admin())
// `user_id`-scoped:
//   USING (user_id = auth.uid() OR is_platform_admin())
//   WITH CHECK (user_id = auth.uid() OR is_platform_admin())
// Public-read (e.g. blog_posts):
//   SELECT to anon/authenticated USING (true)
//   INSERT/UPDATE/DELETE: USING (false) WITH CHECK (false)

function policyFor(tableName, columns) {
  // Heuristics based on columns present:
  const hasBrandId = columns.some(c => /^brand_id\b/i.test(c));
  const hasUserId = columns.some(c => /^user_id\b/i.test(c) && !/"userId"/i.test(c));
  const hasUserIdCamel = columns.some(c => /"userId"/i.test(c));
  const hasContactEmail = columns.some(c => /^contact_email\b/i.test(c));
  const hasEmail = columns.some(c => /^email\b/i.test(c) && !/email_sent/i.test(c));

  // Public-read-only with deny write: blog_posts / blog_resources / roadmap_items (public-facing)
  const publicReadOnly = new Set([
    "blog_posts", "blog_resources", "roadmap_items",
    "newsletter_subscribers", "waitlist",
  ]);
  if (publicReadOnly.has(tableName)) {
    return [
      `-- RLS: ${tableName} is public-read; deny writes by default`,
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS public_read ON ${tableName};`,
      `CREATE POLICY public_read ON ${tableName} FOR SELECT USING (true);`,
      `DROP POLICY IF EXISTS deny_write ON ${tableName};`,
      `CREATE POLICY deny_write ON ${tableName} FOR INSERT WITH CHECK (false);`,
      `DROP POLICY IF EXISTS deny_update ON ${tableName};`,
      `CREATE POLICY deny_update ON ${tableName} FOR UPDATE USING (false) WITH CHECK (false);`,
      `DROP POLICY IF EXISTS deny_delete ON ${tableName};`,
      `CREATE POLICY deny_delete ON ${tableName} FOR DELETE USING (false);`,
    ];
  }

  // verification_token (Auth.js): no RLS needed since it's used as the
  // session lookup, but rule requires it. Lock to service role only.
  if (tableName === "verification_token") {
    return [
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS deny_all ON ${tableName};`,
      `CREATE POLICY deny_all ON ${tableName} FOR ALL USING (false) WITH CHECK (false);`,
    ];
  }

  // users / accounts / sessions: Auth.js tables, scoped by userId.
  if (hasUserIdCamel) {
    return [
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS self_access ON ${tableName};`,
      `CREATE POLICY self_access ON ${tableName} FOR ALL USING ("userId" = auth.uid() OR is_platform_admin()) WITH CHECK ("userId" = auth.uid() OR is_platform_admin());`,
    ];
  }

  // users (Auth.js): self-read own row.
  if (tableName === "users") {
    return [
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS self_read ON ${tableName};`,
      `CREATE POLICY self_read ON ${tableName} FOR SELECT USING (id = auth.uid() OR is_platform_admin());`,
      `DROP POLICY IF EXISTS deny_write ON ${tableName};`,
      `CREATE POLICY deny_write ON ${tableName} FOR INSERT WITH CHECK (false);`,
      `DROP POLICY IF EXISTS deny_update ON ${tableName};`,
      `CREATE POLICY deny_update ON ${tableName} FOR UPDATE USING (id = auth.uid() OR is_platform_admin()) WITH CHECK (id = auth.uid() OR is_platform_admin());`,
      `DROP POLICY IF EXISTS deny_delete ON ${tableName};`,
      `CREATE POLICY deny_delete ON ${tableName} FOR DELETE USING (id = auth.uid() OR is_platform_admin());`,
    ];
  }

  // sessions: self-access by userId column.
  if (tableName === "sessions") {
    return [
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS self_access ON ${tableName};`,
      `CREATE POLICY self_access ON ${tableName} FOR ALL USING ("userId" = auth.uid() OR is_platform_admin()) WITH CHECK ("userId" = auth.uid() OR is_platform_admin());`,
    ];
  }

  // launch_checklist_progress: user-scoped.
  if (tableName === "launch_checklist_progress" && hasUserId) {
    return [
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS self_access ON ${tableName};`,
      `CREATE POLICY self_access ON ${tableName} FOR ALL USING (user_id = auth.uid()::text OR is_platform_admin()) WITH CHECK (user_id = auth.uid()::text OR is_platform_admin());`,
    ];
  }

  // brand-scoped tables: most common.
  if (hasBrandId) {
    return [
      `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
      `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
      `DROP POLICY IF EXISTS tenant_isolation ON ${tableName};`,
      `CREATE POLICY tenant_isolation ON ${tableName} FOR ALL USING (brand_id = current_brand_id() OR is_platform_admin()) WITH CHECK (brand_id = current_brand_id() OR is_platform_admin());`,
    ];
  }

  // Fallback: deny all (safest default).
  return [
    `ALTER TABLE ${tableName} ENABLE ROW LEVEL SECURITY;`,
    `ALTER TABLE ${tableName} FORCE ROW LEVEL SECURITY;`,
    `DROP POLICY IF EXISTS deny_all ON ${tableName};`,
    `CREATE POLICY deny_all ON ${tableName} FOR ALL USING (false) WITH CHECK (false);`,
  ];
}

// Parse a file and return [{name, columns}] for each CREATE TABLE block.
function parseTables(sql) {
  const tables = [];
  // Match CREATE TABLE ... (...);
  const re = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:public\.)?([a-zA-Z_][a-zA-Z0-9_]*)\s*\(([\s\S]*?)\)\s*;/gi;
  let match;
  while ((match = re.exec(sql)) !== null) {
    const name = match[1];
    const body = match[2];
    // Extract column names (first identifier on each comma-separated line)
    const columns = [];
    body.split(",").forEach(line => {
      const trimmed = line.trim();
      if (!trimmed) return;
      if (/^(?:CONSTRAINT|UNIQUE|PRIMARY\s+KEY|FOREIGN\s+KEY|CHECK|INDEX)\b/i.test(trimmed)) return;
      const colMatch = trimmed.match(/^(?:"[^"]+"|`[^`]+`|[a-zA-Z_][a-zA-Z0-9_]*)/);
      if (colMatch) columns.push(colMatch[0]);
    });
    tables.push({ name, columns });
  }
  return tables;
}

const files = [
  "005_water_log.sql",
  "044_square_sync_log.sql",
  "066_auto_square_sync.sql",
  "069_brand_scoping_phase2.sql",
  "070_rls_policy_audit.sql",
  "080_communication_segments.sql",
  "088_brand_features.sql",
  "120_water_alerts.sql",
  "126_founder_pain_log.sql",
  "129_worker_time_tracking.sql",
  "130_time_tracking_pay_period_overtime.sql",
  "133_time_tracking_notifications.sql",
  "134_abandoned_cart_recovery.sql",
  "137_time_tracking_workers_and_tasks.sql",
  "138_time_tracking_logs.sql",
  "204_authjs_tables.sql",
  "XXX_blog_tables.sql",
  "XXX_launch_checklist.sql",
  "XXX_roadmap_tables.sql",
  "XXX_waitlist_table.sql",
];

let totalAdded = 0;
let totalSkipped = 0;

for (const file of files) {
  const filePath = path.join(ARCHIVE_DIR, file);
  if (!fs.existsSync(filePath)) {
    console.log(`skip (not found): ${file}`);
    continue;
  }
  const original = fs.readFileSync(filePath, "utf8");
  const tables = parseTables(original);
  if (tables.length === 0) {
    console.log(`skip (no tables): ${file}`);
    continue;
  }

  // Idempotency check: skip if "ENABLE ROW LEVEL SECURITY" already present for any of the tables.
  const newTables = tables.filter(t => !new RegExp(`ALTER\\s+TABLE\\s+${t.name}\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`, "i").test(original));
  if (newTables.length === 0) {
    console.log(`skip (already done): ${file} — ${tables.length} tables`);
    totalSkipped += tables.length;
    continue;
  }

  const blocks = newTables.map(t => {
    const policy = policyFor(t.name, t.columns);
    return [
      `-- ============================================================`,
      `-- RLS for ${t.name} (added by scripts/fix-archived-rls.js)`,
      `-- Columns: ${t.columns.join(", ")}`,
      `-- ============================================================`,
      ...policy,
    ].join("\n");
  });

  const appended = blocks.join("\n\n") + "\n";
  fs.writeFileSync(filePath, original + "\n" + appended);
  console.log(`patched: ${file} — added RLS for ${newTables.length} table(s): ${newTables.map(t => t.name).join(", ")}`);
  totalAdded += newTables.length;
}

console.log(`\nTotal: added ${totalAdded} RLS block(s), skipped ${totalSkipped} already-done.`);
