import { spawn } from "child_process";
import fs from "fs";
import path from "path";
import { prisma } from "@repo/db/client";

const LOCAL_BACKUP_DIR = path.resolve(process.cwd(), "backups");

const SCHEMA_PATH = path.resolve(__dirname, "../../../../packages/db/prisma/schema.prisma");

function runDbPush(): Promise<void> {
  return new Promise((resolve, reject) => {
    const proc = spawn("npx", ["prisma", "db", "push", "--config", path.resolve(__dirname, "../../../../packages/db/prisma/prisma.config.ts"), "--schema", SCHEMA_PATH, "--accept-data-loss"], {
      cwd: process.cwd(),
      env: process.env,
    });

    let stderr = "";
    proc.stdout.on("data", (d) => console.log(`[db-push] ${d.toString().trim()}`));
    proc.stderr.on("data", (d) => {
      const line = d.toString().trim();
      stderr += line + "\n";
      console.log(`[db-push] ${line}`);
    });
    proc.on("error", (err) => reject(new Error(`Failed to run prisma db push: ${err.message}`)));
    proc.on("close", (code) => {
      if (code !== 0) return reject(new Error(`prisma db push failed (exit ${code}): ${stderr}`));
      console.log("[db-push] Schema synced successfully");
      resolve();
    });
  });
}

function getLatestBackupFile(): string | null {
  if (!fs.existsSync(LOCAL_BACKUP_DIR)) return null;

  const files = fs.readdirSync(LOCAL_BACKUP_DIR)
    .filter((f) => f.endsWith(".zip") || f.endsWith(".sql"))
    .map((f) => ({ name: f, mtime: fs.statSync(path.join(LOCAL_BACKUP_DIR, f)).mtimeMs }))
    .sort((a, b) => b.mtime - a.mtime);

  if (files.length === 0) return null;
  return path.join(LOCAL_BACKUP_DIR, files[0].name);
}

export async function importLatestBackup(): Promise<void> {
  const backupFile = getLatestBackupFile();
  if (!backupFile) {
    throw new Error("No backup files found in backups folder");
  }

  console.log(`[auto-import] Importing ${path.basename(backupFile)}...`);

  try {
    await prisma.$executeRawUnsafe(`DROP SCHEMA public CASCADE`);
    await prisma.$executeRawUnsafe(`CREATE SCHEMA public`);
  } catch (err: any) {
    throw new Error(`Failed to reset schema: ${err.message}`);
  }

  return new Promise((resolve, reject) => {
    const psql = spawn(
      "psql",
      [
        "-h", process.env.DB_HOST || "localhost",
        "-U", process.env.DB_USER || "postgres",
        process.env.DB_NAME || "dental_db",
      ],
      { env: { ...process.env, PGPASSWORD: process.env.DB_PASSWORD } }
    );

    let stderr = "";
    psql.stderr.on("data", (d) => (stderr += d.toString()));

    psql.on("error", (err) => {
      reject(new Error(`Failed to start psql: ${err.message}`));
    });

    psql.on("close", async (code) => {
      if (code !== 0) {
        return reject(new Error(`psql restore failed (exit ${code}): ${stderr}`));
      }

      try {
        await prisma.$disconnect();
        await prisma.$connect();
      } catch (_) {}

      try {
        await runDbPush();
      } catch (err) {
        console.error("[auto-import] db push failed after import:", err);
      }

      console.log(`[auto-import] Successfully imported ${path.basename(backupFile)}`);
      resolve();
    });

    if (backupFile.endsWith(".zip")) {
      const unzip = spawn("unzip", ["-p", backupFile, "*.sql"]);
      unzip.stderr.on("data", (d) => console.warn(`[auto-import] unzip: ${d.toString().trim()}`));
      unzip.on("error", (err) => {
        reject(new Error(`Failed to extract backup zip: ${err.message}`));
      });
      unzip.stdout.pipe(psql.stdin);
    } else {
      const sqlStream = fs.createReadStream(backupFile);
      sqlStream.on("error", (err) => {
        reject(new Error(`Failed to read backup file: ${err.message}`));
      });
      sqlStream.pipe(psql.stdin);
    }
  });
}
