import { SignJWT, jwtVerify } from "jose";
import { randomBytes } from "node:crypto";
import fs from "node:fs/promises";
import path from "node:path";
import { cookies } from "next/headers";
import bcrypt from "bcryptjs";
import { db } from "@/lib/db";
import { ensureProjectRoot } from "@/lib/paths";

const SESSION_COOKIE = "school_session";
let secretPromise: Promise<Uint8Array> | null = null;

/** Use an explicitly configured key or a random, local-only key persisted beside the database. */
async function sessionSecret(): Promise<Uint8Array> {
  if (process.env.AUTH_SECRET) {
    if (Buffer.byteLength(process.env.AUTH_SECRET) < 32) throw new Error("AUTH_SECRET must be at least 32 bytes");
    return new TextEncoder().encode(process.env.AUTH_SECRET);
  }
  if (!secretPromise) {
    secretPromise = (async () => {
      const file = path.join(ensureProjectRoot(), "db", ".auth-secret");
      try {
        const saved = await fs.readFile(file);
        if (saved.byteLength < 32) throw new Error("Stored authentication key is invalid");
        return new Uint8Array(saved);
      } catch (error) {
        if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
      }
      await fs.mkdir(path.dirname(file), { recursive: true });
      const generated = randomBytes(48);
      try {
        await fs.writeFile(file, generated, { flag: "wx", mode: 0o600 });
        return new Uint8Array(generated);
      } catch (error) {
        if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
        return new Uint8Array(await fs.readFile(file));
      }
    })();
  }
  return secretPromise;
}

export type Role = "SUPER_ADMIN" | "ADMIN" | "ACCOUNTANT" | "STAFF";

/** هل الدور إداري أعلى؟ (مدير النظام العام أو مدير مدرسة) */
export function isAdminLike(role: string): boolean {
  return role === "ADMIN" || role === "SUPER_ADMIN";
}

export interface SessionUser {
  id: string;
  username: string;
  fullName: string;
  role: Role;
  /** المدرسة المرتبطة بالمستخدم — null لمدير النظام العام (SUPER_ADMIN) */
  schoolId?: string | null;
  /** الصفحات المسموحة للمستخدم — null/فارغة = الافتراضي حسب الدور */
  allowedViews?: string[] | null;
  sessionVersion?: number;
}

export async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 10);
}

export async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

export async function createSessionToken(user: SessionUser): Promise<string> {
  return new SignJWT({ ...user })
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime("12h")
    .sign(await sessionSecret());
}

export async function setSessionCookie(user: SessionUser) {
  const token = await createSessionToken(user);
  const cookieStore = await cookies();
  cookieStore.set(SESSION_COOKIE, token, {
    httpOnly: true,
    secure: process.env.COOKIE_SECURE === "true",
    sameSite: "strict",
    path: "/",
    maxAge: 60 * 60 * 12,
  });
}

export async function clearSessionCookie() {
  const cookieStore = await cookies();
  cookieStore.set(SESSION_COOKIE, "", { httpOnly: true, path: "/", maxAge: 0 });
}

export async function getSessionUser(): Promise<SessionUser | null> {
  try {
    const cookieStore = await cookies();
    const token = cookieStore.get(SESSION_COOKIE)?.value;
    if (!token) return null;
    const { payload } = await jwtVerify(token, await sessionSecret());
    if (!payload || !payload.id) return null;
    // جلب البيانات الحية من قاعدة البيانات (الدور والمدرسة والصفحات المسموحة — محدثة دائماً
    // حتى لو أصدرت الجلسة قبل ترقية النظام متعدد المدارس، وهذا يعالج الجلسات العالقة)
    const dbUser = await db.user.findUnique({
      where: { id: payload.id as string },
      select: { allowedViews: true, isActive: true, role: true, schoolId: true, deletedAt: true, passwordChangedAt: true },
    });
    if (!dbUser || !dbUser.isActive || dbUser.deletedAt) return null;
    if (dbUser.passwordChangedAt && Number(payload.iat ?? 0) * 1000 + 1000 < dbUser.passwordChangedAt.getTime()) return null;
    let allowedViews: string[] | null = null;
    if (dbUser?.allowedViews) {
      try {
        const parsed = JSON.parse(dbUser.allowedViews);
        if (Array.isArray(parsed)) allowedViews = parsed as string[];
      } catch {
        allowedViews = null;
      }
    }
    return {
      id: payload.id as string,
      username: payload.username as string,
      fullName: payload.fullName as string,
      role: (dbUser.role as Role) ?? (payload.role as Role),
      schoolId: dbUser.schoolId ?? null,
      allowedViews,
    };
  } catch {
    return null;
  }
}

export class ApiError extends Error {
  status: number;
  constructor(message: string, status = 401) {
    super(message);
    this.status = status;
  }
}

/** يتحقق من وجود مستخدم مسجل دخول، وإلا يرمي خطأ */
export async function requireUser(): Promise<SessionUser> {
  const user = await getSessionUser();
  if (!user) throw new ApiError("غير مصرح - يجب تسجيل الدخول", 401);
  // التحقق من أن الحساب لا يزال نشطاً (والبيانات الحية: الدور والمدرسة)
  const dbUser = await db.user.findUnique({ where: { id: user.id } });
  if (!dbUser || !dbUser.isActive || dbUser.deletedAt) throw new ApiError("الحساب غير نشط", 403);
  // دمج الدور والمدرسة الحية من قاعدة البيانات (يعالج الجلسات القديمة الصادرة قبل الترقية)
  return { ...user, role: dbUser.role as Role, schoolId: dbUser.schoolId ?? null };
}

/** يتحقق من أن المستخدم يملك أحد الأدوار المطلوبة — مدير النظام العام (SUPER_ADMIN) يدخل دائماً */
export async function requireRole(...roles: Role[]): Promise<SessionUser> {
  const user = await requireUser();
  if (user.role !== "SUPER_ADMIN" && !roles.includes(user.role)) {
    throw new ApiError("ليست لديك الصلاحية للقيام بهذه العملية", 403);
  }
  return user;
}

/** Role checks protect operations; page checks also enforce the administrator's per-user page grants. */
export async function requirePageRole(view: string, ...roles: Role[]): Promise<SessionUser> {
  const user = await requireRole(...roles);
  if (user.role !== "SUPER_ADMIN" && user.allowedViews?.length && !user.allowedViews.includes(view)) {
    throw new ApiError("لا تملك صلاحية الوصول إلى هذه الصفحة", 403);
  }
  return user;
}

export async function requirePageRoleAny(views: string[], ...roles: Role[]): Promise<SessionUser> {
  const user = await requireRole(...roles);
  if (user.role !== "SUPER_ADMIN" && user.allowedViews?.length && !views.some((view) => user.allowedViews!.includes(view))) {
    throw new ApiError("لا تملك صلاحية الوصول إلى هذه الصفحة", 403);
  }
  return user;
}

export async function requirePageUser(view: string): Promise<SessionUser> {
  const user = await requireUser();
  if (user.role !== "SUPER_ADMIN" && user.allowedViews?.length && !user.allowedViews.includes(view)) {
    throw new ApiError("لا تملك صلاحية الوصول إلى هذه الصفحة", 403);
  }
  return user;
}

/** المدرسة الافتراضية الأولى النشطة (للمدير العام وللإصلاح الذاتي) */
export async function getDefaultSchoolId(): Promise<string | null> {
  const school = await db.school.findFirst({
    where: { isActive: true },
    orderBy: { createdAt: "asc" },
    select: { id: true },
  });
  return school?.id ?? null;
}

export interface SchoolScope {
  user: SessionUser;
  /** المدرسة الفعالية للطلب */
  schoolId: string;
  /** هل الطالب مدير النظام العام؟ (يمكنه تمرير schoolId صريح) */
  isSuperAdmin: boolean;
}

/**
 * يحدد نطاق المدرسة للطلب (v8):
 * - SUPER_ADMIN: المدرسة المطلوبة صراحة عبر schoolId أو المدرسة الافتراضية الأولى
 * - بقية الأدوار: مدرستهم المرتبطة فقط، مع إصلاح ذاتي يربط الحساب اليتيم بأول مدرسة
 * يرمي ApiError إذا لم توجد مدرسة صالحة.
 */
export async function requireSchoolScope(requestedSchoolId?: string | null): Promise<SchoolScope> {
  const user = await requireUser();
  const schoolId = await resolveSchoolId(user, requestedSchoolId);
  return { user, schoolId, isSuperAdmin: user.role === "SUPER_ADMIN" };
}

/**
 * نسخة خفيفة تُستخدم بعد requireRole في المسارات — تحدد schoolId الفعلي للطلب
 * بدون إعادة جلب المستخدم (المستخدم يمرر ناتج requireRole/requireUser).
 */
export async function resolveSchoolId(
  user: SessionUser,
  requestedSchoolId?: string | null
): Promise<string> {
  if (user.role === "SUPER_ADMIN") {
    const sid = (requestedSchoolId ?? "").trim() || (await getDefaultSchoolId());
    if (!sid) throw new ApiError("لا توجد مدارس في النظام - أضف مدرسة أولاً من صفحة المدارس", 400);
    return sid;
  }
  let schoolId = user.schoolId ?? null;
  if (!schoolId) {
    // إصلاح ذاتي: حساب بلا مدرسة يُربط بأول مدرسة نشطة تلقائياً
    schoolId = await getDefaultSchoolId();
    if (schoolId) {
      try {
        await db.user.update({ where: { id: user.id }, data: { schoolId } });
      } catch {
        /* تجاهل */
      }
    }
  }
  if (!schoolId) {
    throw new ApiError("لا توجد مدرسة مرتبطة بحسابك - راجع مدير النظام", 403);
  }
  return schoolId;
}
