/**
 * Test-tenant seed per ADR-0026 PR 2.
 *
 * Run via:
 *   pnpm exec prisma db seed
 *
 * Idempotent: re-running this script preserves existing rows. The
 * checks for "row already there" run against the unique constraints:
 *   - User.entraOid (skipping the cold path of
 *     PersonAndUserProvisioner if the row exists),
 *   - UserScope.(userId, kind, value).
 *
 * The seed creates Person + User + UserScope rows for the 19 personas
 * provisioned in the `apfrd.onmicrosoft.com` test tenant. The
 * persona matrix below is transcribed from
 * `notes/test-tenant-role-assignments.md` (gitignored) — that file
 * remains the human-readable reference; this constant is the source
 * of truth for the seed.
 *
 * **Test-tenant Entra `oid`s** are read from a JSON file pointed at
 * by `TEST_TENANT_PERSONAS_PATH` (or `infra/test-tenant.personas.json`
 * by default). The file is gitignored — copy
 * `infra/test-tenant.personas.example.json` and fill in real values
 * from the Entra admin centre. Missing file → seed skips the
 * test-tenant section cleanly (no error, just a log line).
 *
 * **`Person.source = 'seed'`** for every row created here, per the
 * PERSON_SOURCES catalogue. Differentiates these rows from
 * 'self-signin' (lazy-created at first sign-in) and 'admin-ui' (future
 * scope-seeding admin screen, ADR-0026 PR 2b).
 *
 * **Privileges + functional roles are NOT seeded** — those come from
 * Entra at sign-in (Entra `roles` claim + `groups` claim resolved by
 * `EntraGroupToRoleResolver`). Only scopes are portal-side data.
 */
import { existsSync, readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { PrismaClient } from '@prisma/client';

interface PersonaSeed {
  readonly slug: string;
  readonly email: string;
  readonly displayName: string;
  /**
   * Scope tuples per `notes/test-tenant-role-assignments.md`. The
   * `value` is omitted for valueless kinds (self / siege /
   * unrestricted) — the seed writes an empty string to the DB
   * `value` column, matching the column's default.
   */
  readonly scopes: ReadonlyArray<{ readonly kind: string; readonly value?: string }>;
}

const TENANT_DOMAIN = 'apfrd.onmicrosoft.com';

const PERSONAS: ReadonlyArray<PersonaSeed> = [
  {
    slug: 'admin',
    email: `admin@${TENANT_DOMAIN}`,
    displayName: 'Admin User',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'directeur-bordeaux',
    email: `directeur-bordeaux@${TENANT_DOMAIN}`,
    displayName: 'Directeur Bordeaux',
    scopes: [{ kind: 'etablissement', value: '0330800013' }],
  },
  {
    slug: 'directeur-complexe',
    email: `directeur-complexe@${TENANT_DOMAIN}`,
    displayName: 'Directeur Complexe',
    scopes: [
      { kind: 'etablissement', value: '0330800013' },
      { kind: 'etablissement', value: '0330800021' },
    ],
  },
  {
    slug: 'rh-aquitaine',
    email: `rh-aquitaine@${TENANT_DOMAIN}`,
    displayName: 'RH Aquitaine',
    scopes: [{ kind: 'delegation', value: '33' }],
  },
  {
    slug: 'rh-siege',
    email: `rh-siege@${TENANT_DOMAIN}`,
    displayName: 'RH Siège',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'collaborateur-simple',
    email: `collaborateur-simple@${TENANT_DOMAIN}`,
    displayName: 'Collaborateur Simple',
    scopes: [{ kind: 'self' }],
  },
  {
    slug: 'tresorier-bordeaux',
    email: `tresorier-bordeaux@${TENANT_DOMAIN}`,
    displayName: 'Trésorier Bordeaux',
    scopes: [{ kind: 'delegation', value: '33' }],
  },
  {
    slug: 'dpo',
    email: `dpo@${TENANT_DOMAIN}`,
    displayName: 'DPO',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'it',
    email: `it@${TENANT_DOMAIN}`,
    displayName: 'IT',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'benevole-aquitaine',
    email: `benevole-aquitaine@${TENANT_DOMAIN}`,
    displayName: 'Bénévole Aquitaine',
    scopes: [{ kind: 'delegation', value: '33' }],
  },
  {
    slug: 'chef-equipe-bordeaux',
    email: `chef-equipe-bordeaux@${TENANT_DOMAIN}`,
    displayName: 'Chef Equipe Bordeaux',
    scopes: [{ kind: 'etablissement', value: '0330800013' }],
  },
  {
    slug: 'chef-service-bordeaux',
    email: `chef-service-bordeaux@${TENANT_DOMAIN}`,
    displayName: 'Chef Service Bordeaux',
    scopes: [{ kind: 'etablissement', value: '0330800013' }],
  },
  {
    slug: 'directeur-territorial-aquitaine',
    email: `directeur-territorial-aquitaine@${TENANT_DOMAIN}`,
    displayName: 'Directeur Territorial Aquitaine',
    scopes: [{ kind: 'delegation', value: '33' }],
  },
  {
    slug: 'juriste-siege',
    email: `juriste-siege@${TENANT_DOMAIN}`,
    displayName: 'Juriste Siège',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'rssi',
    email: `rssi@${TENANT_DOMAIN}`,
    displayName: 'RSSI',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'communication-siege',
    email: `communication-siege@${TENANT_DOMAIN}`,
    displayName: 'Communication Siège',
    scopes: [{ kind: 'unrestricted' }],
  },
  {
    slug: 'elu-ca-national',
    email: `elu-ca-national@${TENANT_DOMAIN}`,
    displayName: 'Elu CA National',
    scopes: [{ kind: 'siege' }],
  },
  {
    slug: 'president-cd-aquitaine',
    email: `president-cd-aquitaine@${TENANT_DOMAIN}`,
    displayName: 'Président CD Aquitaine',
    scopes: [{ kind: 'delegation', value: '33' }],
  },
  {
    slug: 'secretaire-cd-aquitaine',
    email: `secretaire-cd-aquitaine@${TENANT_DOMAIN}`,
    displayName: 'Secrétaire CD Aquitaine',
    scopes: [{ kind: 'delegation', value: '33' }],
  },
];

async function main(): Promise<void> {
  const prisma = new PrismaClient();
  try {
    await seedTestTenant(prisma);
  } finally {
    await prisma.$disconnect();
  }
}

async function seedTestTenant(prisma: PrismaClient): Promise<void> {
  const personasPath = resolve(
    process.env['TEST_TENANT_PERSONAS_PATH'] ?? 'infra/test-tenant.personas.json',
  );
  if (!existsSync(personasPath)) {
    console.log(
      `[seed] TEST_TENANT_PERSONAS_PATH (${personasPath}) not found — skipping test-tenant seed.`,
    );
    return;
  }

  let entraOidBySlug: Record<string, string>;
  try {
    const raw = readFileSync(personasPath, 'utf8');
    entraOidBySlug = JSON.parse(raw) as Record<string, string>;
  } catch (err) {
    console.error(
      `[seed] could not parse ${personasPath}: ${err instanceof Error ? err.message : String(err)}`,
    );
    process.exit(1);
  }

  const tenantId = process.env['ENTRA_TENANT_ID'] ?? TENANT_DOMAIN;
  const counts = { personsCreated: 0, usersCreated: 0, scopesCreated: 0, skipped: 0 };

  for (const persona of PERSONAS) {
    const entraOid = entraOidBySlug[persona.slug];
    if (entraOid === undefined || typeof entraOid !== 'string' || entraOid === '') {
      console.warn(`[seed] persona "${persona.slug}" missing oid in ${personasPath} — skipping.`);
      counts.skipped += 1;
      continue;
    }

    const { userId, created } = await ensurePersonAndUser(prisma, persona, entraOid, tenantId);
    if (created) {
      counts.personsCreated += 1;
      counts.usersCreated += 1;
    }
    for (const scope of persona.scopes) {
      const seededNew = await ensureUserScope(prisma, userId, scope.kind, scope.value ?? '');
      if (seededNew) counts.scopesCreated += 1;
    }
  }

  console.log(
    `[seed] test-tenant complete — created ${counts.personsCreated} Person + ${counts.usersCreated} User + ${counts.scopesCreated} UserScope rows; skipped ${counts.skipped} personas (missing oid).`,
  );
}

async function ensurePersonAndUser(
  prisma: PrismaClient,
  persona: PersonaSeed,
  entraOid: string,
  tenantId: string,
): Promise<{ userId: string; personId: string; created: boolean }> {
  const existing = await prisma.user.findUnique({
    where: { entraOid },
    select: { id: true, personId: true },
  });
  if (existing) {
    return { userId: existing.id, personId: existing.personId, created: false };
  }
  const [firstName, lastName] = splitDisplayName(persona.displayName);
  const created = await prisma.user.create({
    data: {
      entraOid,
      tenantId,
      person: {
        create: {
          firstName,
          lastName,
          email: persona.email,
          source: 'seed',
        },
      },
    },
    select: { id: true, personId: true },
  });
  return { userId: created.id, personId: created.personId, created: true };
}

async function ensureUserScope(
  prisma: PrismaClient,
  userId: string,
  kind: string,
  value: string,
): Promise<boolean> {
  const existing = await prisma.userScope.findUnique({
    where: { userId_kind_value: { userId, kind, value } },
  });
  if (existing) return false;
  await prisma.userScope.create({
    data: { userId, kind, value, source: 'seed' },
  });
  return true;
}

function splitDisplayName(displayName: string): [string, string] {
  const trimmed = displayName.trim();
  const space = trimmed.indexOf(' ');
  if (space < 0) return [trimmed, ''];
  return [trimmed.slice(0, space), trimmed.slice(space + 1).trim()];
}

main().catch((err: unknown) => {
  console.error('[seed] failed:', err);
  process.exit(1);
});
