import { execFileSync } from "node:child_process";
import { mkdirSync } from "node:fs";
import { resolve } from "node:path";
import { hash } from "@node-rs/argon2";
import { SystemRole } from "@capakraken/shared";
import { PrismaClient } from "@prisma/client";
import { assertDestructiveDbAllowed } from "./destructive-db-guard.js";
import { loadWorkspaceEnv, resolveWorkspacePath } from "./load-workspace-env.js";
import { buildSystemRoleConfigSeedData } from "./system-role-config-defaults.js";

loadWorkspaceEnv();

const prisma = new PrismaClient();
const DEFAULT_BACKUP_DIR = resolveWorkspacePath("packages", "db", "backups");

interface ResetOptions {
  force: boolean;
  skipBackup: boolean;
  backupDir: string;
  adminEmail: string;
  adminPassword: string;
  adminName: string;
}

function parseArgs(argv: string[]): ResetOptions {
  const options: ResetOptions = {
    force: false,
    skipBackup: false,
    backupDir: DEFAULT_BACKUP_DIR,
    adminEmail: "admin@capakraken.dev",
    adminPassword: "admin123",
    adminName: "CapaKraken Admin",
  };

  for (let index = 0; index < argv.length; index += 1) {
    const argument = argv[index];

    if (argument === "--force") {
      options.force = true;
      continue;
    }

    if (argument === "--skip-backup") {
      options.skipBackup = true;
      continue;
    }

    if (argument === "--backup-dir") {
      options.backupDir = resolve(argv[index + 1] ?? DEFAULT_BACKUP_DIR);
      index += 1;
      continue;
    }

    if (argument === "--admin-email") {
      options.adminEmail = argv[index + 1] ?? options.adminEmail;
      index += 1;
      continue;
    }

    if (argument === "--admin-password") {
      options.adminPassword = argv[index + 1] ?? options.adminPassword;
      index += 1;
      continue;
    }

    if (argument === "--admin-name") {
      options.adminName = argv[index + 1] ?? options.adminName;
      index += 1;
    }
  }

  return options;
}

function createTimestamp() {
  return new Date().toISOString().replace(/[:.]/g, "-");
}

function quoteIdentifier(identifier: string): string {
  return `"${identifier.replace(/"/g, "\"\"")}"`;
}

function createDatabaseBackup(databaseUrl: string, backupDir: string): string {
  mkdirSync(backupDir, { recursive: true });

  const backupPath = resolve(backupDir, `dispo-reset-${createTimestamp()}.dump`);
  execFileSync(
    "pg_dump",
    ["--format=custom", "--file", backupPath, databaseUrl],
    {
      stdio: "inherit",
      env: process.env,
    },
  );

  return backupPath;
}

async function listPublicTables() {
  return prisma.$queryRaw<Array<{ tablename: string }>>`
    SELECT tablename
    FROM pg_tables
    WHERE schemaname = 'public'
      AND tablename <> '_prisma_migrations'
    ORDER BY tablename ASC
  `;
}

async function truncatePublicTables() {
  const tables = await listPublicTables();
  if (tables.length === 0) {
    return [];
  }

  const quotedTables = tables.map((table) => quoteIdentifier(table.tablename)).join(", ");
  await prisma.$executeRawUnsafe(`TRUNCATE TABLE ${quotedTables} RESTART IDENTITY CASCADE`);
  return tables.map((table) => table.tablename);
}

async function bootstrapPlatform(adminEmail: string, adminPassword: string, adminName: string) {
  const passwordHash = await hash(adminPassword);

  const admin = await prisma.user.create({
    data: {
      email: adminEmail,
      name: adminName,
      passwordHash,
      systemRole: SystemRole.ADMIN,
    },
  });

  await prisma.systemSettings.upsert({
    where: { id: "singleton" },
    update: {
      vacationDefaultDays: 28,
    },
    create: {
      id: "singleton",
      vacationDefaultDays: 28,
    },
  });

  for (const config of buildSystemRoleConfigSeedData()) {
    await prisma.systemRoleConfig.upsert({
      where: { role: config.role },
      update: {
        label: config.label,
        description: config.description,
        defaultPermissions: config.defaultPermissions,
        color: config.color,
        sortOrder: config.sortOrder,
      },
      create: config,
    });
  }

  return admin;
}

async function main() {
  const options = parseArgs(process.argv.slice(2));
  const target = assertDestructiveDbAllowed({
    commandName: "db:reset:dispo",
    allowedDatabaseNames: ["capakraken_test", "capakraken_e2e", "capakraken_ci"],
  });
  const databaseUrl = process.env.DATABASE_URL;

  if (!options.force) {
    throw new Error("Refusing to reset the database without --force.");
  }

  if (!databaseUrl) {
    throw new Error("DATABASE_URL is not configured.");
  }

  console.warn(`Resetting disposable database '${target.databaseName}'.`);

  let backupPath: string | null = null;

  if (options.skipBackup) {
    console.warn("Skipping pg_dump backup because --skip-backup was provided.");
  } else {
    try {
      backupPath = createDatabaseBackup(databaseUrl, options.backupDir);
      console.log(`Backup created at ${backupPath}`);
    } catch (error) {
      throw new Error(
        `Backup failed. Install pg_dump or rerun with --skip-backup if this is an intentional disposable environment.\n${String(error)}`,
      );
    }
  }

  const truncatedTables = await truncatePublicTables();
  console.log(`Truncated ${truncatedTables.length} public tables.`);

  const admin = await bootstrapPlatform(options.adminEmail, options.adminPassword, options.adminName);
  console.log(`Bootstrap admin created: ${admin.email}`);

  if (backupPath) {
    console.log(`Database backup: ${backupPath}`);
  }

  console.log("Dispo import reset/bootstrap complete.");
}

main()
  .catch((error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
