// src/lib/auth/org-bootstrap.ts — create a personal org + owner membership,
// and provision the org's default backlink reservoir (the global source catalog).
import "server-only";
import { and, eq } from "drizzle-orm";
import { BACKLINK_SOURCES } from "@/lib/backlink/fixtures";
import { TRIAL_DAYS } from "@/lib/billing/plans";
import { syncOrgSeatsLimit } from "@/lib/billing/seats-limit";
import { member, organization } from "@/lib/db/auth-schema";
import { chunk } from "@/lib/db/chunk";
import { db } from "@/lib/db/client";
import { linkSources } from "@/lib/db/domain-schema";

// Ré-exporté pour compat : la logique elle-même vit dans sys-account.ts (pur,
// sans `server-only`) pour rester testable sans mocker le runtime serveur.
export { shouldBootstrapOrg } from "./sys-account";

/** Part of a namespaced source id after the org prefix (`org__suffix`), or the id itself. */
function suffixOf(id: string): string {
  const i = id.indexOf("__");
  return i >= 0 ? id.slice(i + 2) : id;
}

function slugify(name: string): string {
  return (
    name
      .toLowerCase()
      .normalize("NFD")
      .replace(/\p{Diacritic}/gu, "")
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/(^-|-$)/g, "") || "org"
  );
}

/** Create a fresh organization owned by `userId`; returns the new org id. */
export async function createOrgForUser(
  userId: string,
  name: string,
): Promise<string> {
  const orgId = crypto.randomUUID();
  const slug = `${slugify(name)}-${orgId.slice(0, 8)}`;
  const now = new Date();
  // App-level trial (no card, no Stripe): only the user's FIRST owned org gets
  // the 14-day trial — owning another org already means the trial was consumed.
  const [existingOwned] = await db
    .select({ id: member.id })
    .from(member)
    .where(and(eq(member.userId, userId), eq(member.role, "owner")))
    .limit(1);
  const trialEndsAt = existingOwned
    ? null
    : new Date(now.getTime() + TRIAL_DAYS * 24 * 60 * 60 * 1000);
  // Atomic: never leave an organization without its owner membership.
  await db.transaction(async (tx) => {
    await tx.insert(organization).values({
      id: orgId,
      name: name || "Mon organisation",
      slug,
      createdAt: now,
      trialEndsAt,
    });
    // Materialize the seats backstop (sec audit A2) BEFORE the owner insert
    // below — floored to 1 (see resolveFreshSeatsLimit's doc), so this never
    // blocks the founding owner, first org (trialing) or second+ (expired)
    // alike.
    await syncOrgSeatsLimit(orgId, tx);
    await tx.insert(member).values({
      id: crypto.randomUUID(),
      organizationId: orgId,
      userId,
      role: "owner",
      createdAt: now,
    });
    // Seed the default backlink reservoir for the org (ids namespaced per org so
    // the same catalog can be provisioned to every org without PK collisions).
    // Source of truth: the curated global catalog (sources flagged global=true
    // by the super-admin); fallback to the static fixtures when it's empty.
    const catalog = await tx
      .select()
      .from(linkSources)
      .where(eq(linkSources.global, true));
    const rows =
      catalog.length > 0
        ? catalog.map((s) => ({
            id: `${orgId}__${suffixOf(s.id)}`,
            orgId,
            platform: s.platform,
            url: s.url,
            domainNorm: s.domainNorm,
            kind: s.kind,
            da: s.da,
            effort: s.effort,
            linkType: s.linkType,
            language: s.language,
            themes: s.themes,
            tags: s.tags,
            flags: s.flags,
            note: s.note,
            global: false, // les copies clients ne sont pas globales
          }))
        : BACKLINK_SOURCES.map((s) => ({
            id: `${orgId}__${s.id}`,
            orgId,
            platform: s.platform,
            url: s.url,
            domainNorm: s.domainNorm,
            kind: s.kind,
            da: s.da,
            effort: s.effort,
            linkType: s.linkType,
            language: s.language,
            themes: s.themes,
            tags: s.tags,
            flags: s.flags,
            note: s.note ?? null,
            global: false,
          }));
    // Chunked like the other bulk inserts (ELEVA-73): the global catalog can
    // outgrow the ~65 535 bind-parameter cap.
    for (const slice of chunk(rows, 500)) {
      await tx.insert(linkSources).values(slice);
    }
  });
  return orgId;
}
