import { createHmac, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { getStaffUser } from "@/lib/db";

type StaffSession = { role: "staff"; uid: number; exp: number };
type PatientSession = { role: "patient"; mobileKey: string; exp: number };

const STAFF_COOKIE = "ih_staff";
const PATIENT_COOKIE = "ih_patient";
const MAX_AGE = 60 * 60 * 12;

function sessionSecret() {
  const fromEnv = process.env.SESSION_SECRET?.trim();
  if (fromEnv && fromEnv.length >= 16) return fromEnv;
  if (process.env.NODE_ENV !== "production") return "dev-only-session-secret-change-me";
  if (process.env.NEXT_PHASE === "phase-production-build") return "build-time-placeholder-secret";
  throw new Error("SESSION_SECRET must be set to at least 16 characters in production");
}

function cookieSecure() {
  return (process.env.APP_BASE_URL || "").startsWith("https://");
}

function sign(payload: object) {
  const body = Buffer.from(JSON.stringify(payload)).toString("base64url");
  const sig = createHmac("sha256", sessionSecret()).update(body).digest("base64url");
  return `${body}.${sig}`;
}

function read<T>(raw: string | undefined): T | null {
  if (!raw) return null;
  const dot = raw.lastIndexOf(".");
  if (dot <= 0) return null;
  const body = raw.slice(0, dot);
  const sig = raw.slice(dot + 1);
  const expected = createHmac("sha256", sessionSecret()).update(body).digest("base64url");
  const left = Buffer.from(sig);
  const right = Buffer.from(expected);
  if (left.length !== right.length || !timingSafeEqual(left, right)) return null;
  try {
    return JSON.parse(Buffer.from(body, "base64url").toString("utf8")) as T;
  } catch {
    return null;
  }
}

function cookieOptions() {
  return {
    httpOnly: true,
    sameSite: "lax" as const,
    secure: cookieSecure(),
    path: "/",
    maxAge: MAX_AGE,
  };
}

export async function getStaffSession() {
  const jar = await cookies();
  const payload = read<StaffSession>(jar.get(STAFF_COOKIE)?.value);
  if (!payload || payload.role !== "staff" || payload.exp < Date.now() / 1000) return null;
  return payload;
}

export async function getPatientSession() {
  const jar = await cookies();
  const payload = read<PatientSession>(jar.get(PATIENT_COOKIE)?.value);
  if (!payload || payload.role !== "patient" || payload.exp < Date.now() / 1000) return null;
  return payload;
}

export async function requireStaff() {
  const session = await getStaffSession();
  if (!session) redirect("/staff/login");
  const user = getStaffUser(session.uid);
  if (!user) redirect("/staff/login");
  return user;
}

export async function setStaffSession(uid: number) {
  const jar = await cookies();
  jar.set(
    STAFF_COOKIE,
    sign({ role: "staff", uid, exp: Math.floor(Date.now() / 1000) + MAX_AGE }),
    cookieOptions(),
  );
}

export async function setPatientSession(mobileKey: string) {
  const jar = await cookies();
  jar.set(
    PATIENT_COOKIE,
    sign({ role: "patient", mobileKey, exp: Math.floor(Date.now() / 1000) + MAX_AGE }),
    cookieOptions(),
  );
}

export async function clearStaffSession() {
  const jar = await cookies();
  jar.set(STAFF_COOKIE, "", { ...cookieOptions(), maxAge: 0 });
}

export async function clearPatientSession() {
  const jar = await cookies();
  jar.set(PATIENT_COOKIE, "", { ...cookieOptions(), maxAge: 0 });
}

export function hashCode(code: string) {
  const salt = randomBytes(16).toString("hex");
  const hash = scryptSync(code, salt, 32).toString("hex");
  return `${salt}:${hash}`;
}

export function checkCode(code: string, stored: string) {
  const [salt, hash] = stored.split(":");
  if (!salt || !hash) return false;
  const next = scryptSync(code, salt, 32);
  const prev = Buffer.from(hash, "hex");
  if (next.length !== prev.length) return false;
  return timingSafeEqual(next, prev);
}

export function generateCode() {
  const value = randomBytes(4).readUInt32BE(0) % 1000000;
  return String(value).padStart(6, "0");
}

export function generateToken() {
  return randomBytes(24).toString("base64url");
}
