import crypto from 'node:crypto';
import prisma from '../src/config/db.js';
import logger from '../src/config/logger.js';
import { MODULE_CATALOG } from '../src/config/modules.js';
import { firebaseAuth } from '../src/config/firebase.js';
import { env } from '../src/config/env.js';
import { ROLES, seedPermissions as seedRoleAccessPermissions, seedRoleAccess } from './seedData.js';

const DEFAULT_SUPER_ADMIN_EMAIL = 'superadmin@blenaxis.dev';

// Representative permission set (units only, for now) proving the
// requirePermission() mechanism end to end — see src/middlewares/requirePermission.ts.
// Extending this to the other master tables (materials, equipment, ...) is
// the same pattern, just more entries; not done yet on purpose.
const PERMISSIONS = [
  { name: 'units.create', description: 'Create a unit', module: 'units' },
  { name: 'units.update', description: 'Update a unit', module: 'units' },
  { name: 'units.delete', description: 'Delete a unit', module: 'units' },
];

// blenaxis-super-admin's onboarding wizard "Plan" step — see the seed values
// in that repo's src/mocks/data.ts, mirrored here as the real starting data.
const PLANS = [
  {
    code: 'STARTER', name: 'Starter', description: 'For a single team getting started.',
    priceMonthly: 24999, currency: 'INR',
    includedModuleKeys: ['tenant_admin', 'project_core', 'tasks'],
    maxUsers: 25, maxProjects: 3, storageGb: 50,
  },
  {
    code: 'GROWTH', name: 'Growth', description: 'For a growing multi-project developer.',
    priceMonthly: 74999, currency: 'INR',
    includedModuleKeys: ['tenant_admin', 'project_core', 'tasks', 'planning', 'documents', 'boq'],
    maxUsers: 100, maxProjects: 15, storageGb: 250,
  },
  {
    code: 'ENTERPRISE', name: 'Enterprise', description: 'Every module, for a large portfolio.',
    priceMonthly: 249999, currency: 'INR',
    includedModuleKeys: MODULE_CATALOG.map((module) => module.key),
    maxUsers: 1000, maxProjects: 100, storageGb: 2000,
  },
];

// The one platform-owner account, created once in both Firebase and the DB —
// self-healing on every rerun: if someone deletes the Firebase account, it's
// recreated (with a new uid) and the DB row's firebaseUid is reconciled to
// match, rather than erroring or creating a duplicate. No env var is
// required — SUPER_ADMIN_EMAIL/PASSWORD only override the defaults below.
async function seedSuperAdmin(adminRoleId: number) {
  const email = env.SUPER_ADMIN_EMAIL ?? DEFAULT_SUPER_ADMIN_EMAIL;

  let firebaseUser;
  try {
    firebaseUser = await firebaseAuth.getUserByEmail(email);
  } catch (error) {
    if ((error as { code?: string }).code !== 'auth/user-not-found') throw error;
    // Only generated (and only known) when the account doesn't exist yet —
    // an existing account's password is never touched by this script.
    const password = env.SUPER_ADMIN_PASSWORD ?? crypto.randomBytes(12).toString('hex');
    firebaseUser = await firebaseAuth.createUser({ email, password, emailVerified: true });
    logger.info(`Created Firebase super admin account: ${email}`);
    if (!env.SUPER_ADMIN_PASSWORD) {
      logger.warn(`Generated super admin password (save this now, it will not be shown again): ${password}`);
    }
  }

  const existing = await prisma.user.findUnique({ where: { email } });
  if (!existing) {
    await prisma.user.create({
      data: { email, name: 'Super Admin', firebaseUid: firebaseUser.uid, roleId: adminRoleId, emailVerified: true, isActive: true },
    });
    logger.info(`Seeded super admin DB row: ${email}`);
    return;
  }

  if (existing.firebaseUid !== firebaseUser.uid || existing.roleId !== adminRoleId || !existing.isActive) {
    await prisma.user.update({
      where: { id: existing.id },
      data: { firebaseUid: firebaseUser.uid, roleId: adminRoleId, isActive: true },
    });
    logger.info(`Reconciled super admin DB row: ${email}`);
    return;
  }

  logger.info(`Super admin already seeded: ${email}`);
}

async function main() {
  const roleByName: Record<string, { id: number }> = {};
  for (const role of ROLES) {
    roleByName[role.name] = await prisma.role.upsert({ where: { name: role.name }, update: {}, create: role });
  }
  logger.info(`Seeded roles: ${ROLES.map((role) => role.name).join(', ')}`);

  // The 10 business roles' label, data scope, module access and tenant
  // permissions (organization-admin, director, ...) — see DEFAULT_ROLES in
  // src/config/access.ts. Without this, every business role has no modules
  // and no permissions, so the tenant app's sidebar shows nothing for them.
  await seedRoleAccessPermissions(prisma);
  await seedRoleAccess(prisma);
  logger.info('Seeded tenant role access (modules & permissions) for the default business roles');

  const permissionByName: Record<string, { id: number }> = {};
  for (const permission of PERMISSIONS) {
    permissionByName[permission.name] = await prisma.permission.upsert({
      where: { name: permission.name }, update: {}, create: permission,
    });
  }
  logger.info(`Seeded permissions: ${PERMISSIONS.map((permission) => permission.name).join(', ')}`);

  // admin gets every seeded permission — keeps admin's access unchanged
  // if/when a route is switched from authorize('admin') to requirePermission(...).
  const adminRoleId = roleByName.admin.id;
  await prisma.rolePermission.createMany({
    data: Object.values(permissionByName).map((permission) => ({ roleId: adminRoleId, permissionId: permission.id })),
    skipDuplicates: true,
  });
  logger.info(`Granted all seeded permissions to 'admin'`);

  for (const plan of PLANS) {
    await prisma.plan.upsert({ where: { code: plan.code }, update: plan, create: plan });
  }
  logger.info(`Seeded plans: ${PLANS.map((plan) => plan.code).join(', ')}`);

  for (const module_ of MODULE_CATALOG) {
    await prisma.platformModule.upsert({ where: { key: module_.key }, update: module_, create: module_ });
  }
  logger.info(`Seeded ${MODULE_CATALOG.length} platform modules`);

  await prisma.platformSettings.upsert({ where: { id: 1 }, update: {}, create: { id: 1 } });
  logger.info('Seeded platform settings (defaults)');

  await seedSuperAdmin(adminRoleId);
}

main()
  .catch((error) => {
    logger.error('Seed failed', { message: error instanceof Error ? error.message : String(error) });
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
