import { PrismaClient } from '@prisma/client';
import {
  bindResourceToPage,
  getPageResourceIds,
  grantRoleAccess,
  upsertPage,
} from '../scripts/lib/page-helpers';
import { readPrismaModels } from '../scripts/prismaModels';

/**
 * Models whose Page record should be marked `isVirtual = true`,
 * i.e. routes / resources / role grants are still seeded so that the page
 * remains reachable by URL and protected by RBAC, but the page is hidden
 * from the auto-generated 「系统管理 / 数据管理」 menu.
 *
 * Use this when a model's CRUD page is exposed elsewhere (e.g. embedded
 * inside another custom admin page) instead of as its own menu entry.
 */
const HIDDEN_FROM_DATA_MNGT_MENU = new Set<string>([
  // Surfaced via a button in /admin/agenda-jobs (Cron Jobs page).
  'agendaJob',
  // Surfaced via a button in /admin/workflow (Workflow list page).
  'workflowEventOutbox',
]);

// ─── Main Seed Function ───────────────────────────────────────────────────────

/**
 * For every non-system Prisma model found in config/prisma/ (excluding system.prisma):
 *
 *  1. Upsert a Resource record           (resource.code = camelCase model name)
 *  2. Upsert a Page record               (parent = dataMngt, path = /admin/data-mngt/<slug>)
 *  3. Upsert a PageResource link         (page ↔ resource, full actions)
 *  4. Upsert a RolePages record          (SYSTEM_ADMIN → page)
 *  5. Upsert a RoleResource record       (SYSTEM_ADMIN → resource, full actions)
 *
 * Safe to re-run — every write uses upsert / skipDuplicates.
 *
 * Prerequisites: SYSTEM_ADMIN role and dataMngt page must already exist
 * (created by seedBootstrap). The standalone entry point at the bottom of
 * this file delegates to seed/index.ts main() in case the DB is fresh, so
 * `ts-node seed/data-mngt.seed.ts` always works.
 */
export async function seedDataMngtPages(prisma: PrismaClient): Promise<void> {
  const models = readPrismaModels();

  if (models.length === 0) {
    console.log(
      '[seedDataMngtPages] No non-system models found — nothing to seed.',
    );
    return;
  }

  // ── Anchors ────────────────────────────────────────────────────────────────
  // SYSTEM_ADMIN role and dataMngt page are created by seedBootstrap, which
  // seed/index.ts main() always runs before this function. If they're missing,
  // it means this function was called out of order — fail fast with a clear msg.
  const systemAdminRole = await prisma.role.findUnique({
    where: { name: 'SYSTEM_ADMIN' },
  });
  if (!systemAdminRole) {
    throw new Error(
      '[seedDataMngtPages] SYSTEM_ADMIN role missing. Run seedBootstrap (via `yarn seed`) first.',
    );
  }

  const dataMngtPage = await prisma.page.findUnique({
    where: { code: 'dataMngt' },
  });
  if (!dataMngtPage) {
    throw new Error(
      '[seedDataMngtPages] dataMngt page missing. Run seedBootstrap (via `yarn seed`) first.',
    );
  }

  // ── Process models ─────────────────────────────────────────────────────────
  for (const [index, model] of models.entries()) {
    console.log(`[seedDataMngtPages] ── ${model.name} ──`);

    const pagePath = `/admin/data-mngt/${model.slug}`;
    const hideFromMenu = HIDDEN_FROM_DATA_MNGT_MENU.has(model.code);

    // 1 + 2 + 3: Upsert page, resource, and PageResource link
    const page = await upsertPage(
      prisma,
      {
        code: model.code,
        name: model.name,
        sortOrder: 2000 + index * 10,
        parentId: dataMngtPage.id,
        path: pagePath,
        isVirtual: hideFromMenu,
      },
      // keep path in sync with model name on update;
      // also force isVirtual when in the hide-from-menu list so existing
      // rows get reconciled (otherwise the default upsertPage update is {}).
      { path: pagePath, ...(hideFromMenu && { isVirtual: true }) },
    );
    console.log(
      `[seedDataMngtPages]   Page upserted: ${page.code} → ${pagePath} (id=${
        page.id
      })${hideFromMenu ? ' [hidden from menu]' : ''}`,
    );

    const resource = await bindResourceToPage(
      prisma,
      page.id,
      model.code,
      `${model.name} resource (auto-generated by seedDataMngtPages)`,
    );
    console.log(
      `[seedDataMngtPages]   Resource + PageResource upserted: ${resource.code} (id=${resource.id})`,
    );

    // 4 + 5: Grant SYSTEM_ADMIN access to this page and resource
    const resourceIds = await getPageResourceIds(prisma, page.id);
    await grantRoleAccess(prisma, systemAdminRole.id, page.id, resourceIds);
    console.log(
      `[seedDataMngtPages]   RolePages + RoleResource upserted: SYSTEM_ADMIN → ${page.code}`,
    );
  }

  console.log(
    `[seedDataMngtPages] ✅ Done. Seeded ${models.length} model(s) under /admin/data-mngt.`,
  );
}

// ─── Standalone Entry Point ───────────────────────────────────────────────────
// Called directly by `postgenerate:dev` (via ts-node) for incremental sync
// after `yarn generate:dev` adds new business models. Also callable manually.
//
// Fresh-DB safety: if SYSTEM_ADMIN / dataMngt are missing, we delegate to
// seed/index.ts main() which runs the full seed pipeline (which itself calls
// seedDataMngtPages). Otherwise we just run seedDataMngtPages incrementally.

if (require.main === module) {
  const prisma = new PrismaClient();

  (async () => {
    const adminRole = await prisma.role.findUnique({
      where: { name: 'SYSTEM_ADMIN' },
    });
    if (!adminRole) {
      console.log(
        '[seedDataMngtPages] Fresh DB detected — delegating to full seed pipeline…',
      );
      const { main } = await import('./index');
      await main();
      return;
    }
    await seedDataMngtPages(prisma);
  })()
    .catch((e) => {
      console.error('[seedDataMngtPages] Fatal error:', e);
      process.exit(1);
    })
    .finally(() => prisma.$disconnect());
}
