import { db } from "@workspace/db";
import { BUILTIN_APP_SLUGS } from "@workspace/db/built-in-apps";
import {
  usersTable,
  rolesTable,
  userRolesTable,
  permissionsTable,
  rolePermissionsTable,
  appsTable,
  appPermissionsTable,
  serviceCategoriesTable,
  servicesTable,
  groupsTable,
  userGroupsTable,
  groupAppsTable,
  groupRolesTable,
  systemSettingsTable,
  executiveMeetingsTable,
  executiveMeetingAttendeesTable,
  executiveMeetingNotificationsTable,
} from "@workspace/db";
import { eq, sql } from "drizzle-orm";
import bcrypt from "bcryptjs";

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

  // Create roles
  const [adminRole] = await db
    .insert(rolesTable)
    .values({ name: "admin", descriptionAr: "مدير النظام", descriptionEn: "System Administrator", isSystem: 1 })
    .onConflictDoNothing()
    .returning();

  const [userRole] = await db
    .insert(rolesTable)
    .values({ name: "user", descriptionAr: "مستخدم عادي", descriptionEn: "Regular User", isSystem: 1 })
    .onConflictDoNothing()
    .returning();

  console.log("Roles created");

  // Create permissions
  const permissions = [
    { name: "apps:manage", descriptionAr: "إدارة التطبيقات", descriptionEn: "Manage Apps" },
    { name: "services:manage", descriptionAr: "إدارة الخدمات", descriptionEn: "Manage Services" },
    { name: "users:manage", descriptionAr: "إدارة المستخدمين", descriptionEn: "Manage Users" },
    { name: "orders.receive", descriptionAr: "استلام الطلبات", descriptionEn: "Receive service orders" },
  ];

  await db.insert(permissionsTable).values(permissions).onConflictDoNothing();
  const allInsertedPerms = await db.select().from(permissionsTable);

  console.log("Permissions created");

  // Create order_receiver role
  const [orderReceiverRole] = await db
    .insert(rolesTable)
    .values({
      name: "order_receiver",
      descriptionAr: "مستلم الطلبات",
      descriptionEn: "Order Receiver",
      isSystem: 1,
    })
    .onConflictDoNothing()
    .returning();

  const allRoles = await db.select().from(rolesTable);
  const adminRoleResolved = adminRole ?? allRoles.find((r) => r.name === "admin");
  const userRoleResolved = userRole ?? allRoles.find((r) => r.name === "user");
  const orderReceiverResolved =
    orderReceiverRole ?? allRoles.find((r) => r.name === "order_receiver");

  // Assign all permissions to admin role
  if (adminRoleResolved && allInsertedPerms.length > 0) {
    await db
      .insert(rolePermissionsTable)
      .values(allInsertedPerms.map((p) => ({ roleId: adminRoleResolved.id, permissionId: p.id })))
      .onConflictDoNothing();
  }

  // Assign orders:receive permission to order_receiver role
  if (orderReceiverResolved && allInsertedPerms.length > 0) {
    const ordersPerm = allInsertedPerms.find((p) => p.name === "orders.receive");
    if (ordersPerm) {
      await db
        .insert(rolePermissionsTable)
        .values({ roleId: orderReceiverResolved.id, permissionId: ordersPerm.id })
        .onConflictDoNothing();
    }
  }

  // First-run install state. The canonical bootstrap + legacy backfill
  // live in the api-server startup hook (ensureSystemSettingsBootstrap),
  // so they run on every boot regardless of whether seed is invoked.
  // Here we only read the current flag to decide whether to create the
  // seeded admin/user pair below.
  const adminPassword = process.env.SEED_ADMIN_PASSWORD;
  const userPassword = process.env.SEED_USER_PASSWORD;

  await db
    .insert(systemSettingsTable)
    .values({ id: 1, installed: false })
    .onConflictDoNothing();

  const sysRows = await db
    .select()
    .from(systemSettingsTable)
    .where(eq(systemSettingsTable.id, 1))
    .limit(1);
  const installedFlag = sysRows[0]?.installed ?? false;

  const existingAdminRows = await db
    .select({ id: usersTable.id })
    .from(userRolesTable)
    .innerJoin(usersTable, eq(usersTable.id, userRolesTable.userId))
    .innerJoin(rolesTable, eq(rolesTable.id, userRolesTable.roleId))
    .where(eq(rolesTable.name, "admin"))
    .limit(1);
  const adminAlreadyExists = existingAdminRows.length > 0;

  let adminUser: { id: number } | undefined;
  let regularUser: { id: number } | undefined;

  const skipUserCreation = installedFlag || adminAlreadyExists;
  const haveSeedEnv = Boolean(adminPassword && userPassword);

  if (skipUserCreation) {
    console.log(
      "Skipping seeded admin/user — system already installed or an admin already exists.",
    );
  } else if (!haveSeedEnv) {
    const host = process.env.PUBLIC_BASE_URL ?? "https://<host>";
    console.log(
      `[seed] No SEED_ADMIN_PASSWORD/SEED_USER_PASSWORD set — first-run wizard required at ${host}/setup`,
    );
  } else {
    const adminHash = await bcrypt.hash(adminPassword!, 10);
    const insertedAdmin = await db
      .insert(usersTable)
      .values({
        username: "admin",
        email: "admin@tx.local",
        passwordHash: adminHash,
        displayNameAr: "مدير النظام",
        displayNameEn: "System Admin",
        preferredLanguage: "ar",
        isActive: true,
      })
      .onConflictDoNothing()
      .returning();
    adminUser = insertedAdmin[0];

    const userHash = await bcrypt.hash(userPassword!, 10);
    const insertedRegular = await db
      .insert(usersTable)
      .values({
        username: "ahmed",
        email: "ahmed@tx.local",
        passwordHash: userHash,
        displayNameAr: "أحمد محمد",
        displayNameEn: "Ahmed Mohammed",
        preferredLanguage: "ar",
        isActive: true,
      })
      .onConflictDoNothing()
      .returning();
    regularUser = insertedRegular[0];

    // Mark install complete so the wizard does not appear. The
    // bootstrap insert at the top of this script always wrote
    // (id=1, installed=false), so a plain onConflictDoNothing here
    // would leave installed=false. Use DO UPDATE to actually flip it.
    await db
      .insert(systemSettingsTable)
      .values({ id: 1, installed: true, installedAt: new Date() })
      .onConflictDoUpdate({
        target: systemSettingsTable.id,
        set: {
          installed: true,
          installedAt: sql`COALESCE(${systemSettingsTable.installedAt}, NOW())`,
          updatedAt: new Date(),
        },
      });

    console.log("Users created");
  }

  // Assign roles
  const roles = await db.select().from(rolesTable);
  const adminRoleRow = roles.find((r) => r.name === "admin");
  const userRoleRow = roles.find((r) => r.name === "user");

  if (adminUser && adminRoleRow) {
    await db
      .insert(userRolesTable)
      .values({ userId: adminUser.id, roleId: adminRoleRow.id })
      .onConflictDoNothing();
  }

  if (regularUser && userRoleRow) {
    await db
      .insert(userRolesTable)
      .values({ userId: regularUser.id, roleId: userRoleRow.id })
      .onConflictDoNothing();
  }

  console.log("Roles assigned");

  // Create apps
  const apps = [
    {
      slug: "services",
      nameAr: "خدماتي",
      nameEn: "My Services",
      descriptionAr: "اطلب خدماتك الداخلية بسهولة",
      descriptionEn: "Order your internal services easily",
      iconName: "Coffee",
      route: "/services",
      color: "#f59e0b",
      isActive: true,
      isSystem: true,
      sortOrder: 1,
    },
    {
      slug: "notifications",
      nameAr: "الإشعارات",
      nameEn: "Notifications",
      descriptionAr: "إدارة الإشعارات والتنبيهات",
      descriptionEn: "Manage your notifications and alerts",
      iconName: "Bell",
      route: "/notifications",
      // #603: was #8b5cf6 (purple). Switched to cyan so no seeded app
      // ships with a purple tile. Existing installs are migrated by
      // scripts/src/update-app-colors.ts (only rows still on the old
      // default are touched — admin customisations are preserved).
      color: "#06b6d4",
      // #571: ship the Notifications app DISABLED by default so fresh
      // installs don't surface a redundant tile alongside the bell icon
      // already in the top bar. The bell stays the canonical entry
      // point; admins can flip this on from app settings if they want
      // the launcher tile too. Insert here uses onConflictDoNothing,
      // so existing environments that already have the app enabled
      // are NOT affected — only new inserts pick up the default.
      isActive: false,
      isSystem: true,
      sortOrder: 3,
    },
    {
      slug: "admin",
      nameAr: "لوحة التحكم",
      nameEn: "Admin Panel",
      descriptionAr: "إدارة النظام والمستخدمين",
      descriptionEn: "System and user management",
      iconName: "Settings",
      route: "/admin",
      color: "#ef4444",
      isActive: true,
      isSystem: true,
      sortOrder: 4,
    },
    {
      slug: "notes",
      nameAr: "الملاحظات",
      nameEn: "Notes",
      descriptionAr: "ملاحظاتك الشخصية ورسائلك بين الزملاء",
      descriptionEn: "Personal notes and messages with colleagues",
      iconName: "StickyNote",
      route: "/notes",
      color: "#eab308",
      isActive: true,
      isSystem: false,
      sortOrder: 8,
    },
    {
      slug: "calendar",
      nameAr: "التقويم",
      nameEn: "Calendar",
      descriptionAr: "الاجتماعات والمواعيد",
      descriptionEn: "Meetings and appointments",
      iconName: "Calendar",
      route: "/calendar",
      color: "#3b82f6",
      // #571: ship the Calendar app DISABLED by default. Admins can
      // enable it from app settings when they actually want a
      // calendar surface. Insert uses onConflictDoNothing so existing
      // environments where calendar is already enabled stay enabled.
      isActive: false,
      isSystem: false,
      sortOrder: 5,
    },
    {
      slug: "documents",
      nameAr: "المستندات",
      nameEn: "Documents",
      descriptionAr: "مستودع المستندات والملفات",
      descriptionEn: "Documents and files repository",
      iconName: "FileText",
      route: "/documents",
      color: "#64748b",
      // #577: same policy as notifications/calendar — ship Documents
      // DISABLED on fresh installs. There is no Documents surface the
      // user is actively using yet and the tile cluttered the home
      // launcher. Insert uses onConflictDoNothing, so existing
      // environments keep whatever the current row says — see
      // scripts/src/disable-deprecated-apps.ts for the one-shot that
      // flips already-seeded rows off.
      isActive: false,
      isSystem: false,
      sortOrder: 6,
    },
    {
      slug: "executive-meetings",
      nameAr: "الاجتماعات",
      nameEn: "Meetings",
      descriptionAr: "جدولة الاجتماعات ومتابعتها",
      descriptionEn: "Schedule and track meetings",
      iconName: "CalendarClock",
      route: "/meetings",
      // #603: was #0B1E3F (dark navy) which fell through to the default
      // blue tile gradient, making Meetings indistinguishable from
      // Admin/Notes on the home screen. Switched to green so the four
      // default tiles are visually distinct. Existing installs are
      // migrated by scripts/src/update-app-colors.ts.
      color: "#22c55e",
      isActive: true,
      isSystem: false,
      sortOrder: 7,
    },
  ];

  // Drift guard: every built-in slug (those with hardcoded routes in the
  // SPA, see lib/db/src/built-in-apps.ts) that this seed actually creates
  // here must use the same `route` the SPA expects. We don't require ALL
  // built-in slugs to be seeded (some, like "notes" / "my-orders" /
  // "orders-incoming", are user-created later), but if we DO seed one,
  // the route must match the hardcoded SPA route.
  const expectedBuiltinRoutes: Record<string, string> = {
    services: "/services",
    notifications: "/notifications",
    admin: "/admin",
    notes: "/notes",
    "my-orders": "/my-orders",
    "orders-incoming": "/orders/incoming",
    "executive-meetings": "/meetings",
    calendar: "/calendar",
    documents: "/documents",
  };
  for (const a of apps) {
    if ((BUILTIN_APP_SLUGS as readonly string[]).includes(a.slug)) {
      const expected = expectedBuiltinRoutes[a.slug];
      if (expected && a.route !== expected) {
        throw new Error(
          `seed: built-in app "${a.slug}" must use route "${expected}" (got "${a.route}"). ` +
            `Built-in slugs are listed in lib/db/src/built-in-apps.ts and have hardcoded SPA routes in artifacts/tx-os/src/App.tsx.`,
        );
      }
    }
  }

  const insertedApps = await db.insert(appsTable).values(apps).onConflictDoNothing().returning();
  console.log("Apps created");

  // Force the executive-meetings row onto the canonical /meetings route
  // AND onto the current display name. The slug is in BUILTIN_APP_SLUGS,
  // so the SPA owns its route — any drift in the DB (including legacy
  // /executive-meetings or other experimental values) would launch the
  // home tile at a path that has no React route and silently break the
  // app. `onConflictDoNothing` above skips updating existing rows, so
  // this UPDATE is what carries the route + name migration into
  // already-deployed environments. The WHERE by slug keeps it
  // idempotent across re-seeds. Names are kept in sync with the
  // hardcoded values in the `apps` array above so admins on existing
  // deployments don't have to rename the tile by hand.
  await db
    .update(appsTable)
    .set({
      route: "/meetings",
      nameAr: "الاجتماعات",
      nameEn: "Meetings",
      descriptionAr: "جدولة الاجتماعات ومتابعتها",
      descriptionEn: "Schedule and track meetings",
    })
    .where(eq(appsTable.slug, "executive-meetings"));

  // Restrict admin app to users with apps:manage permission
  const allPerms = await db.select().from(permissionsTable);
  const appsManagePerm = allPerms.find((p) => p.name === "apps:manage");
  const adminApp = insertedApps.find((a) => a.slug === "admin")
    ?? (await db.select().from(appsTable).where(eq(appsTable.slug, "admin")))[0];

  if (adminApp && appsManagePerm) {
    await db
      .insert(appPermissionsTable)
      .values({ appId: adminApp.id, permissionId: appsManagePerm.id })
      .onConflictDoNothing();
    console.log("App permissions set: admin app restricted to apps:manage permission");
  }

  // Create service categories
  const categories = [
    { nameAr: "مشروبات", nameEn: "Beverages", sortOrder: 1 },
    { nameAr: "أغذية", nameEn: "Food", sortOrder: 2 },
    { nameAr: "أخرى", nameEn: "Other", sortOrder: 3 },
  ];

  const insertedCategories = await db
    .insert(serviceCategoriesTable)
    .values(categories)
    .onConflictDoNothing()
    .returning();

  const beveragesCat = insertedCategories[0];

  // ----- Services: idempotent renames for legacy rows -----
  // Older deployments seeded "Arabic Coffee" / "قهوة عربية" and
  // "قهوة بلاك". Rename them BEFORE the insert below so the insert's
  // onConflictDoNothing (target: nameEn) sees the new "Saudi Coffee"
  // name already present and skips it — avoiding a unique-constraint
  // collision on services.name_en. Never overwrite an imageUrl that
  // an admin has already customized.
  const existingServices = await db.select().from(servicesTable);
  const legacyArabicCoffee = existingServices.find(
    (s) => s.nameEn === "Arabic Coffee",
  );
  if (legacyArabicCoffee) {
    await db
      .update(servicesTable)
      .set({
        nameAr: "قهوة سعودي",
        nameEn: "Saudi Coffee",
        imageUrl:
          legacyArabicCoffee.imageUrl &&
          legacyArabicCoffee.imageUrl !== "service-images/arabic-coffee.png"
            ? legacyArabicCoffee.imageUrl
            : "service-images/saudi-coffee.png",
      })
      .where(eq(servicesTable.id, legacyArabicCoffee.id));
  }
  const legacyBlackCoffee = existingServices.find(
    (s) => s.nameEn === "Black Coffee" && s.nameAr === "قهوة بلاك",
  );
  if (legacyBlackCoffee) {
    await db
      .update(servicesTable)
      .set({ nameAr: "بلاك كوفي" })
      .where(eq(servicesTable.id, legacyBlackCoffee.id));
  }

  // Create services
  const services = [
    {
      categoryId: beveragesCat?.id ?? null,
      nameAr: "شاي",
      nameEn: "Tea",
      imageUrl: "service-images/tea.png",
      price: "0.00",
      isAvailable: true,
      sortOrder: 1,
    },
    {
      categoryId: beveragesCat?.id ?? null,
      nameAr: "قهوة سعودي",
      nameEn: "Saudi Coffee",
      imageUrl: "service-images/saudi-coffee.png",
      price: "0.00",
      isAvailable: true,
      sortOrder: 2,
    },
    {
      categoryId: beveragesCat?.id ?? null,
      nameAr: "بلاك كوفي",
      nameEn: "Black Coffee",
      imageUrl: "service-images/black-coffee.png",
      price: "0.00",
      isAvailable: true,
      sortOrder: 3,
    },
    {
      categoryId: beveragesCat?.id ?? null,
      nameAr: "عصير طازج",
      nameEn: "Fresh Juice",
      imageUrl: "service-images/juice.png",
      price: "5.00",
      isAvailable: true,
      sortOrder: 5,
    },
  ];

  await db.insert(servicesTable).values(services).onConflictDoNothing({ target: servicesTable.nameEn });
  console.log("Services created");

  // Backfill default imageUrl on existing rows that have a NULL imageUrl
  // (the insert above is onConflictDoNothing, so existing rows aren't
  // touched). Only fills NULL — admin-customized URLs are never
  // overwritten. Re-reads the table so newly-inserted rows are included.
  const allServicesAfterInsert = await db.select().from(servicesTable);
  for (const seed of services) {
    if (!seed.imageUrl) continue;
    const existing = allServicesAfterInsert.find((s) => s.nameEn === seed.nameEn);
    if (existing && !existing.imageUrl) {
      await db
        .update(servicesTable)
        .set({ imageUrl: seed.imageUrl })
        .where(eq(servicesTable.id, existing.id));
    }
  }
  console.log("Service images backfilled for NULL rows");

  // ----- Groups: idempotent migration -----
  await db
    .insert(groupsTable)
    .values([
      {
        name: "Admins",
        descriptionAr: "مديرو النظام",
        descriptionEn: "System administrators",
        isSystem: 1,
      },
      {
        name: "Tx",
        descriptionAr: "فريق إعداد وتقديم الطلبات",
        descriptionEn: "Team that prepares and delivers orders",
        isSystem: 1,
      },
      {
        name: "Everyone",
        descriptionAr: "كل المستخدمين",
        descriptionEn: "All users",
        isSystem: 1,
      },
    ])
    .onConflictDoNothing();

  // One-time migration: rename legacy "TeaBoy" group to "Tx" if it still exists
  // and the new "Tx" group hasn't been created yet.
  const existingGroups = await db.select().from(groupsTable);
  const legacy = existingGroups.find((g) => g.name === "TeaBoy");
  const newOne = existingGroups.find((g) => g.name === "Tx");
  if (legacy && !newOne) {
    await db
      .update(groupsTable)
      .set({ name: "Tx" })
      .where(eq(groupsTable.id, legacy.id));
  }

  const allGroups = await db.select().from(groupsTable);
  const adminsGroup = allGroups.find((g) => g.name === "Admins");
  const txGroup = allGroups.find((g) => g.name === "Tx");
  const everyoneGroup = allGroups.find((g) => g.name === "Everyone");

  const allRolesNow = await db.select().from(rolesTable);
  const adminRoleNow = allRolesNow.find((r) => r.name === "admin");
  const orderReceiverNow = allRolesNow.find((r) => r.name === "order_receiver");

  // Group → roles
  if (adminsGroup && adminRoleNow) {
    await db
      .insert(groupRolesTable)
      .values({ groupId: adminsGroup.id, roleId: adminRoleNow.id })
      .onConflictDoNothing();
  }
  if (txGroup && orderReceiverNow) {
    await db
      .insert(groupRolesTable)
      .values({ groupId: txGroup.id, roleId: orderReceiverNow.id })
      .onConflictDoNothing();
  }

  // Group → apps
  const allAppsNow = await db.select().from(appsTable);
  if (adminsGroup) {
    await db
      .insert(groupAppsTable)
      .values(allAppsNow.map((a) => ({ groupId: adminsGroup.id, appId: a.id })))
      .onConflictDoNothing();
  }
  // The Tx receivers group gets the services app (where receivers see incoming orders)
  if (txGroup) {
    const servicesApp = allAppsNow.find((a) => a.slug === "services");
    if (servicesApp) {
      await db
        .insert(groupAppsTable)
        .values({ groupId: txGroup.id, appId: servicesApp.id })
        .onConflictDoNothing();
    }
  }

  // Map existing users to groups idempotently
  const allUsersNow = await db.select({ id: usersTable.id }).from(usersTable);
  if (everyoneGroup && allUsersNow.length > 0) {
    await db
      .insert(userGroupsTable)
      .values(allUsersNow.map((u) => ({ userId: u.id, groupId: everyoneGroup.id })))
      .onConflictDoNothing();
  }

  if (adminsGroup && adminRoleNow) {
    const adminUserRows = await db
      .select({ userId: userRolesTable.userId })
      .from(userRolesTable)
      .where(eq(userRolesTable.roleId, adminRoleNow.id));
    if (adminUserRows.length > 0) {
      await db
        .insert(userGroupsTable)
        .values(adminUserRows.map((r) => ({ userId: r.userId, groupId: adminsGroup.id })))
        .onConflictDoNothing();
    }
  }

  if (txGroup && orderReceiverNow) {
    const receiverRows = await db
      .select({ userId: userRolesTable.userId })
      .from(userRolesTable)
      .where(eq(userRolesTable.roleId, orderReceiverNow.id));
    if (receiverRows.length > 0) {
      await db
        .insert(userGroupsTable)
        .values(receiverRows.map((r) => ({ userId: r.userId, groupId: txGroup.id })))
        .onConflictDoNothing();
    }
  }
  console.log("Groups created and existing users mapped");

  // Executive meetings: roles
  const execRoles = [
    {
      name: "executive_ceo",
      descriptionAr: "الرئيس التنفيذي",
      descriptionEn: "Chief Executive Officer",
      isSystem: 1,
    },
    {
      name: "executive_office_manager",
      descriptionAr: "مدير المكتب التنفيذي",
      descriptionEn: "Executive Office Manager",
      isSystem: 1,
    },
    {
      name: "executive_coord_lead",
      descriptionAr: "رئيس التنسيق والتوثيق",
      descriptionEn: "Coordination Lead",
      isSystem: 1,
    },
    {
      name: "executive_coordinator",
      descriptionAr: "منسق المكتب التنفيذي",
      descriptionEn: "Executive Coordinator",
      isSystem: 1,
    },
    {
      name: "executive_viewer",
      descriptionAr: "مشاهد الاجتماعات التنفيذية",
      descriptionEn: "Executive Meetings Viewer",
      isSystem: 1,
    },
  ];
  await db.insert(rolesTable).values(execRoles).onConflictDoNothing();
  console.log("Executive Meetings roles created");

  // Executive meetings: gate the home-screen icon to executive roles only.
  // This adds a single permission, grants it to admin + the 5 executive
  // roles, and links it to the `executive-meetings` app via app_permissions
  // so getVisibleAppsForUser will hide the icon for everyone else.
  // All three inserts are idempotent.
  await db
    .insert(permissionsTable)
    .values({
      name: "executive_meetings.access",
      descriptionAr: "الوصول إلى وحدة الاجتماعات التنفيذية",
      descriptionEn: "Access the Executive Meetings module",
    })
    .onConflictDoNothing();

  const [execAccessPerm] = await db
    .select()
    .from(permissionsTable)
    .where(eq(permissionsTable.name, "executive_meetings.access"));

  if (execAccessPerm) {
    const execRoleNames = [
      "admin",
      "executive_ceo",
      "executive_office_manager",
      "executive_coord_lead",
      "executive_coordinator",
      "executive_viewer",
    ];
    const allRolesNow = await db.select().from(rolesTable);
    const execRoleRows = allRolesNow.filter((r) => execRoleNames.includes(r.name));
    if (execRoleRows.length > 0) {
      await db
        .insert(rolePermissionsTable)
        .values(
          execRoleRows.map((r) => ({
            roleId: r.id,
            permissionId: execAccessPerm.id,
          })),
        )
        .onConflictDoNothing();
    }

    const [execApp] = await db
      .select()
      .from(appsTable)
      .where(eq(appsTable.slug, "executive-meetings"));
    if (execApp) {
      await db
        .insert(appPermissionsTable)
        .values({ appId: execApp.id, permissionId: execAccessPerm.id })
        .onConflictDoNothing();
      console.log(
        "App permissions set: executive-meetings app restricted to executive_meetings.access permission",
      );
    }
  }

  // Notes: gate the home-screen icon behind a dedicated permission so a
  // freshly seeded deployment does NOT auto-show Notes to every regular
  // user. By default only the `admin` role gets the permission; admins
  // then grant `notes.access` to whichever roles/users they want from
  // the admin panel. Same three-step pattern as executive_meetings.access
  // above — all inserts are idempotent.
  await db
    .insert(permissionsTable)
    .values({
      name: "notes.access",
      descriptionAr: "الوصول إلى تطبيق الملاحظات",
      descriptionEn: "Access the Notes app",
    })
    .onConflictDoNothing();

  const [notesAccessPerm] = await db
    .select()
    .from(permissionsTable)
    .where(eq(permissionsTable.name, "notes.access"));

  if (notesAccessPerm) {
    const adminRoleForNotes = (await db.select().from(rolesTable))
      .find((r) => r.name === "admin");
    if (adminRoleForNotes) {
      await db
        .insert(rolePermissionsTable)
        .values({
          roleId: adminRoleForNotes.id,
          permissionId: notesAccessPerm.id,
        })
        .onConflictDoNothing();
    }

    const [notesApp] = await db
      .select()
      .from(appsTable)
      .where(eq(appsTable.slug, "notes"));
    if (notesApp) {
      await db
        .insert(appPermissionsTable)
        .values({ appId: notesApp.id, permissionId: notesAccessPerm.id })
        .onConflictDoNothing();
      console.log(
        "App permissions set: notes app restricted to notes.access permission",
      );
    }
  }

  // Executive meetings: sample data for today (idempotent per-date).
  //
  // Opt-in only — a vanilla self-hosted install should start with an
  // empty Executive Meetings module. Set `SEED_DEMO_MEETINGS=true` in
  // your environment when bootstrapping a demo / staging deploy that
  // benefits from realistic-looking sample data.
  const seedDemoMeetings =
    (process.env.SEED_DEMO_MEETINGS ?? "").toLowerCase() === "true";
  const today = new Date().toISOString().slice(0, 10);
  const existingForToday = seedDemoMeetings
    ? await db
        .select({ id: executiveMeetingsTable.id })
        .from(executiveMeetingsTable)
        .where(eq(executiveMeetingsTable.meetingDate, today))
    : [];
  if (!seedDemoMeetings) {
    console.log(
      "Executive Meetings demo data skipped (set SEED_DEMO_MEETINGS=true to enable)",
    );
  } else if (existingForToday.length === 0) {
    {
    const sampleMeetings: Array<{
      meeting: {
        dailyNumber: number;
        titleAr: string;
        titleEn: string;
        meetingDate: string;
        startTime: string;
        endTime: string;
        platform: "none" | "webex" | "teams" | "zoom" | "other";
        isHighlighted: number;
        status: string;
      };
      attendees: Array<{
        name: string;
        attendanceType: "internal" | "virtual" | "external";
      }>;
    }> = [
      {
        meeting: {
          dailyNumber: 1,
          titleAr: "اجتماع افتتاحي للمكتب التنفيذي",
          titleEn: "Executive Office Opening Meeting",
          meetingDate: today,
          startTime: "10:00",
          endTime: "10:30",
          platform: "none",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "أ. سعد العتيبي", attendanceType: "internal" },
          { name: "أ. خالد الشهري", attendanceType: "internal" },
        ],
      },
      {
        meeting: {
          dailyNumber: 2,
          titleAr: "اجتماع بشأن خطة التحول الرقمي",
          titleEn: "Digital Transformation Plan Meeting",
          meetingDate: today,
          startTime: "11:00",
          endTime: "11:30",
          platform: "webex",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "د. محمد الزهراني", attendanceType: "virtual" },
          { name: "أ. عبدالعزيز القحطاني", attendanceType: "virtual" },
          { name: "أ. ناصر الدوسري", attendanceType: "virtual" },
          { name: "م. فهد المالكي", attendanceType: "virtual" },
        ],
      },
      {
        meeting: {
          dailyNumber: 3,
          titleAr: "اجتماع تحضيري لاجتماع مجلس الإدارة",
          titleEn: "Board Meeting Preparation",
          meetingDate: today,
          startTime: "11:40",
          endTime: "12:10",
          platform: "none",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "أ. تركي السبيعي", attendanceType: "internal" },
          { name: "أ. ماجد الحربي", attendanceType: "internal" },
          { name: "أ. عبدالله البقمي", attendanceType: "internal" },
          { name: "أ. سلطان الغامدي", attendanceType: "internal" },
        ],
      },
      {
        meeting: {
          dailyNumber: 4,
          titleAr: "اجتماع بشأن عرض الخطة الخاصة بالقطاعين",
          titleEn: "Two-Sector Plan Presentation",
          meetingDate: today,
          startTime: "13:00",
          endTime: "13:30",
          platform: "none",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "أ. بدر العنزي", attendanceType: "internal" },
          { name: "أ. هاني المطيري", attendanceType: "internal" },
          { name: "أ. وليد الرشيدي", attendanceType: "internal" },
          { name: "أ. يوسف الجهني", attendanceType: "internal" },
          { name: "أ. عبدالرحمن الشمري", attendanceType: "internal" },
        ],
      },
      {
        meeting: {
          dailyNumber: 5,
          titleAr: "اجتماع مع أ. عبدالله بشأن المتابعة",
          titleEn: "Follow-up Meeting with Mr. Abdullah",
          meetingDate: today,
          startTime: "13:30",
          endTime: "13:50",
          platform: "none",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [{ name: "أ. عبدالله الزهراني", attendanceType: "internal" }],
      },
      {
        meeting: {
          dailyNumber: 6,
          titleAr: "اجتماع دوري بشأن مواضيع مكتب الإدارة",
          titleEn: "Periodic Office Topics Meeting",
          meetingDate: today,
          startTime: "14:00",
          endTime: "14:30",
          platform: "none",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "أ. مازن الفيفي", attendanceType: "internal" },
          { name: "أ. سامي العسيري", attendanceType: "internal" },
          { name: "أ. أحمد البلوي", attendanceType: "internal" },
        ],
      },
      {
        meeting: {
          dailyNumber: 7,
          titleAr: "اجتماع بشأن إطار اعتماد البرامج",
          titleEn: "Program Accreditation Framework",
          meetingDate: today,
          startTime: "14:40",
          endTime: "15:10",
          platform: "none",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "د. عبدالمجيد الحارثي", attendanceType: "internal" },
          { name: "د. منصور القرني", attendanceType: "internal" },
          { name: "أ. فيصل الزهراني", attendanceType: "internal" },
        ],
      },
      {
        meeting: {
          dailyNumber: 8,
          titleAr: "اجتماع بشأن استراتيجية الموارد البشرية",
          titleEn: "HR Strategy Meeting",
          meetingDate: today,
          startTime: "15:30",
          endTime: "16:30",
          platform: "webex",
          isHighlighted: 1,
          status: "scheduled",
        },
        attendees: [
          { name: "د. سعود الشهراني", attendanceType: "virtual" },
          { name: "أ. عبدالإله الحربي", attendanceType: "virtual" },
          { name: "أ. خالد العنزي", attendanceType: "virtual" },
          { name: "أ. أحمد المالكي", attendanceType: "internal" },
          { name: "أ. ناصر القحطاني", attendanceType: "internal" },
          { name: "أ. سعيد الغامدي", attendanceType: "internal" },
          { name: "أ. علي الزهراني", attendanceType: "internal" },
          { name: "أ. محمد البقمي", attendanceType: "internal" },
        ],
      },
      {
        meeting: {
          dailyNumber: 9,
          titleAr: "اجتماع بشأن متابعة المشاريع المشتركة",
          titleEn: "Joint Projects Follow-up",
          meetingDate: today,
          startTime: "16:40",
          endTime: "17:10",
          platform: "webex",
          isHighlighted: 0,
          status: "scheduled",
        },
        attendees: [
          { name: "أ. يزيد العتيبي", attendanceType: "virtual" },
          { name: "أ. تركي السهلي", attendanceType: "virtual" },
          { name: "أ. عبدالله الزيد", attendanceType: "internal" },
        ],
      },
    ];

    const insertedMeetings: number[] = [];
    for (const { meeting, attendees } of sampleMeetings) {
      const [inserted] = await db
        .insert(executiveMeetingsTable)
        .values(meeting)
        .returning();
      if (!inserted) continue;
      insertedMeetings.push(inserted.id);
      await db.insert(executiveMeetingAttendeesTable).values(
        attendees.map((a, idx) => ({
          meetingId: inserted.id,
          name: a.name,
          attendanceType: a.attendanceType,
          sortOrder: idx,
        })),
      );
    }
    console.log("Executive Meetings sample data created for today");

    // Seed example notification rules so the Notifications tab has
    // representative content (one row per type/status combination so
    // the page never appears empty in a fresh install).
    if (insertedMeetings.length > 0) {
      const adminId =
        (
          await db
            .select({ id: usersTable.id })
            .from(usersTable)
            .where(eq(usersTable.username, "admin"))
        )[0]?.id ?? null;
      const now = new Date();
      const minutes = (m: number) =>
        new Date(now.getTime() + m * 60 * 1000);
      const samples: Array<{
        meetingId: number;
        userId: number | null;
        notificationType: string;
        scheduledAt: Date | null;
        sentAt: Date | null;
        status: string;
      }> = [
        {
          meetingId: insertedMeetings[0]!,
          userId: adminId,
          notificationType: "meeting_reminder_15m",
          scheduledAt: minutes(15),
          sentAt: null,
          status: "pending",
        },
        {
          meetingId: insertedMeetings[0]!,
          userId: adminId,
          notificationType: "meeting_reminder_60m",
          scheduledAt: minutes(60),
          sentAt: null,
          status: "pending",
        },
      ];
      if (insertedMeetings[1]) {
        samples.push({
          meetingId: insertedMeetings[1]!,
          userId: adminId,
          notificationType: "meeting_invite",
          scheduledAt: minutes(-30),
          sentAt: minutes(-25),
          status: "sent",
        });
      }
      await db.insert(executiveMeetingNotificationsTable).values(samples);
      console.log("Executive Meetings notification samples created");
    }
    }
  } else {
    console.log("Executive Meetings sample data already exists for today, skipping");
  }

  console.log("\n✅ Seeding complete!");
  console.log("\nDemo accounts seeded (passwords from env, not logged):");
  console.log("  Admin: username=admin");
  console.log("  User:  username=ahmed");

  process.exit(0);
}

main().catch((err) => {
  console.error(err);
  process.exit(1);
});
