import { NextRequest, NextResponse } from "next/server";
import fs from "node:fs";
import path from "node:path";
import { spawn } from "node:child_process";
import AdmZip from "adm-zip";
import { db } from "@/lib/db";
import { requirePageRole, ApiError } from "@/lib/auth";
import { logAudit } from "@/lib/audit";
import { handleApiError } from "@/lib/api-helpers";
import { ensureProjectRoot } from "@/lib/paths";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

/** يقارن إصدارين من نمط 7.5.1 أو 7.5.1-test — يعيد سالب/صفر/موجب */
function compareVersions(a: string, b: string): number {
  const pa = a.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
  const pb = b.split("-")[0].split(".").map((n) => parseInt(n, 10) || 0);
  for (let i = 0; i < 3; i++) {
    if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
  }
  return 0;
}

/**
 * رفع حزمة تحديث (UPDATE zip) وتطبيقها:
 *  1) تحقق صلاحيات + ملف zip سليم
 *  2) فحص الحزمة داخل الذاكرة: version.json + .next/BUILD_ID + منع db/custom.db
 *  3) فك الضغط إلى update-staging/package
 *  4) إطلاق scripts/apply-update.mjs منفصلاً — يحفظ البيانات، يبدّل الملفات، ويطلب من Passenger إعادة التشغيل
 *  5) الرد فوراً للمتصفح — الواجهة تتابع الحالة عبر /api/system/update-status
 */
export async function POST(req: NextRequest) {
  try {
    const user = await requirePageRole("settings", "ADMIN");
    const root = ensureProjectRoot();

    const form = await req.formData();
    const file = form.get("file");
    if (!(file instanceof File)) throw new ApiError("لم يُرفق ملف حزمة التحديث", 400);
    if (file.size < 1000) throw new ApiError("الملف المرفق صغير جداً — ليس حزمة تحديث صحيحة", 400);
    if (file.size > 400 * 1024 * 1024) throw new ApiError("الحزمة أكبر من الحد المسموح (400 ميجابايت)", 400);

    // قراءة الإصدار الحالي
    let fromVersion = "؟";
    try {
      fromVersion = String(JSON.parse(fs.readFileSync(path.join(root, "version.json"), "utf8")).version);
    } catch { /* نستخدم ؟ */ }

    // فحص الحزمة داخل الذاكرة قبل فك أي شيء
    let zip: AdmZip;
    try {
      zip = new AdmZip(Buffer.from(await file.arrayBuffer()));
    } catch {
      throw new ApiError("ملف الحزمة تالف أو ليس ملف ZIP صحيحاً", 400);
    }
    const entries = zip.getEntries();
    const names = new Set(entries.map((e) => e.entryName));

    const versionEntry = entries.find((e) => e.entryName === "version.json");
    if (!versionEntry) throw new ApiError("الحزمة لا تحتوي version.json — ليست حزمة تحديث صحيحة", 400);
    let toVersion = "؟";
    let releaseName = "";
    try {
      const v = JSON.parse(versionEntry.getData().toString("utf8"));
      toVersion = String(v.version ?? "؟");
      releaseName = String(v.releaseName ?? "");
    } catch {
      throw new ApiError("version.json داخل الحزمة تالف", 400);
    }
    if (![...names].some((n) => n === ".next/BUILD_ID" || n === ".next/standalone/.next/BUILD_ID")) {
      throw new ApiError("الحزمة لا تحتوي نسخة نظام مبنية (.next/BUILD_ID) — يجب رفع حزمة التحديث الرسمية", 400);
    }
    // حماية قاعدة البيانات: أي حزمة تحتوي db/custom.db تُرفض نهائياً
    if ([...names].some((n) => n.startsWith("db/") && n.endsWith("custom.db"))) {
      throw new ApiError("الحزمة تحتوي ملف قاعدة بيانات — مرفوضة لأمان بياناتك", 400);
    }

    const cmp = compareVersions(toVersion, fromVersion);
    const warnings: string[] = [];
    if (cmp < 0) throw new ApiError(`لا يمكن الرجوع لإصدار أقدم (${toVersion} أقدم من ${fromVersion})`, 400);
    if (cmp === 0) warnings.push("هذه الحزمة بنفس إصدار النظام الحالي — سيُعاد تطبيقها");

    // فك الضغط إلى update-staging/package
    const stagingRoot = path.join(root, "update-staging");
    fs.rmSync(stagingRoot, { recursive: true, force: true });
    const pkgDir = path.join(stagingRoot, "package");
    fs.mkdirSync(pkgDir, { recursive: true });
    zip.extractAllTo(pkgDir, true);
    if (!fs.existsSync(path.join(pkgDir, ".next", "BUILD_ID"))) {
      throw new ApiError("فشل فك ضغط الحزمة — أعد رفعها من جديد", 500);
    }

    await logAudit(user, {
      action: "CREATE",
      entity: "SystemUpdate",
      entityId: toVersion,
      details: `رفع حزمة تحديث من ${fromVersion} إلى ${toVersion}${releaseName ? ` — ${releaseName}` : ""}`,
    });

    // في cPanel يدير Passenger دورة حياة الخادم؛ قتله من داخل التطبيق يقتل/يعطل
    // المساعد المنفصل. نتركه يعمل ثم نطلب إعادة تشغيل Passenger بملف tmp/restart.txt.
    const port = process.env.PORT || "3000";
    // بيئة cPanel لا تضمن تمرير متغيرات Passenger إلى Next.js. في الإنتاج
    // نستخدم restart.txt حتى لا يقتل Passenger عملية التحديث مع الخادم.
    const passengerManaged = process.env.NODE_ENV === "production" || Boolean(
      process.env.PASSENGER_APP_ENV || process.env.PASSENGER_APP_GROUP_NAME || process.env.PASSENGER_APP_ROOT
    );
    const updaterArgs = [path.join(root, "scripts", "apply-update.mjs")];
    if (passengerManaged) updaterArgs.push("--passenger");
    else updaterArgs.push("--pid", String(process.pid));
    updaterArgs.push("--port", port, "--from", fromVersion, "--to", toVersion);
    const child = spawn(process.execPath, updaterArgs, {
      cwd: root,
      detached: true,
      stdio: "ignore",
      env: { ...process.env },
    });
    child.unref();

    return NextResponse.json({
      ok: true,
      fromVersion,
      toVersion,
      warnings,
      message: "تم التحقق من الحزمة بنجاح — جارٍ تطبيق التحديث، لا تغلق الصفحة",
    });
  } catch (err) {
    return handleApiError(err);
  }
}
