import { randomInt } from "node:crypto";
import { eq, inArray, isNull } from "drizzle-orm";
import {
  db,
  membershipIdSettingsTable,
  organizationMembershipIdSettingsTable,
  usersTable,
} from "./index.js";

function normalizePrefix(value: string) {
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed.toUpperCase() : "WWN";
}

function normalizeDigits(value: number) {
  const rounded = Number.isFinite(value) ? Math.floor(value) : 7;
  if (rounded < 4) return 4;
  if (rounded > 12) return 12;
  return rounded;
}

function randomDigits(length: number) {
  let out = "";
  for (let i = 0; i < length; i += 1) {
    out += randomInt(0, 10).toString();
  }
  return out;
}

function buildMembershipRegistrationId(prefix: string, digits: number) {
  return `${normalizePrefix(prefix)}-${randomDigits(normalizeDigits(digits))}`;
}

async function main() {
  const [platformSettings] = await db
    .select()
    .from(membershipIdSettingsTable)
    .where(eq(membershipIdSettingsTable.id, 1))
    .limit(1);

  const fallback = {
    prefix: platformSettings?.prefix ?? "WWN",
    digits: platformSettings?.digits ?? 7,
  };

  const users = await db
    .select({
      id: usersTable.id,
      membershipType: usersTable.membershipType,
      organizationId: usersTable.organizationId,
    })
    .from(usersTable)
    .where(isNull(usersTable.membershipRegistrationId));

  if (users.length === 0) {
    process.stdout.write("No users missing membership registration IDs.\n");
    return;
  }

  const orgIds = Array.from(
    new Set(users.map((u) => u.organizationId).filter((v): v is number => typeof v === "number")),
  );

  const orgSettingsRows =
    orgIds.length === 0
      ? []
      : await db
          .select()
          .from(organizationMembershipIdSettingsTable)
          .where(inArray(organizationMembershipIdSettingsTable.organizationId, orgIds));

  const orgSettingsMap = new Map<number, { prefix: string; digits: number }>();
  for (const row of orgSettingsRows) {
    orgSettingsMap.set(row.organizationId, {
      prefix: row.prefix,
      digits: row.digits,
    });
  }

  let updated = 0;
  for (const user of users) {
    const orgSettings =
      typeof user.organizationId === "number"
        ? orgSettingsMap.get(user.organizationId) ?? null
        : null;
    const settings = orgSettings ?? fallback;

    let assigned = false;
    for (let attempt = 0; attempt < 25; attempt += 1) {
      const candidate = buildMembershipRegistrationId(settings.prefix, settings.digits);
      try {
        await db
          .update(usersTable)
          .set({
            membershipRegistrationId: candidate,
            updatedAt: new Date(),
          })
          .where(eq(usersTable.id, user.id));
        assigned = true;
        updated += 1;
        break;
      } catch (err: any) {
        if (err?.code === "23505") {
          continue;
        }
        throw err;
      }
    }

    if (!assigned) {
      throw new Error(`Could not assign membership registration ID for user ${user.id}.`);
    }
  }

  process.stdout.write(`Assigned membership registration IDs for ${updated} users.\n`);
}

main().catch((err) => {
  process.stderr.write(err instanceof Error ? err.message : String(err));
  process.stderr.write("\n");
  process.exitCode = 1;
});
