import { createHash, randomBytes } from "crypto";
import { prisma } from "@pmap/database";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";

export const sessionCookieName = "pmap_session";
export const sessionDurationMs = 1000 * 60 * 60 * 12;

export type AuthSession = {
  id: string;
  email: string;
  roles: string[];
  participantProfileId: string | null;
};

export function hashSessionToken(token: string) {
  return createHash("sha256").update(token).digest("hex");
}

export function createSessionToken() {
  return randomBytes(32).toString("base64url");
}

export function isAdminRole(role: string) {
  return ["SUPER_ADMIN", "ADMIN", "STAFF"].includes(role);
}

export function userHomePath(roles: string[]) {
  if (roles.some(isAdminRole)) return "/admin";
  if (roles.includes("PARTICIPANT")) return "/participant";
  if (roles.some((role) => role.includes("EXHIBITOR"))) return "/admin/exhibitor-accounts";
  return "/";
}

export function safeRedirectPath(value: string | null | undefined, roles: string[]) {
  const fallback = userHomePath(roles);
  if (!value || !value.startsWith("/") || value.startsWith("//")) return fallback;
  if (value.startsWith("/admin") && !roles.some(isAdminRole)) return fallback;
  if (value.startsWith("/participant") && !roles.includes("PARTICIPANT")) return fallback;
  if (value.startsWith("/login") || value.startsWith("/admin/login")) return fallback;
  return value;
}

export function loginPathForRedirect(redirectTo: string) {
  const path = redirectTo.startsWith("/admin") ? "/admin/login" : "/login";
  return `${path}?redirect=${encodeURIComponent(redirectTo)}`;
}

export async function createUserSession(userId: string, request: Request) {
  const token = createSessionToken();
  const expiresAt = new Date(Date.now() + sessionDurationMs);
  await prisma.userSession.create({
    data: {
      userId,
      tokenHash: hashSessionToken(token),
      userAgent: request.headers.get("user-agent")?.slice(0, 255) ?? null,
      ipAddress: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim().slice(0, 80) ?? null,
      expiresAt
    }
  });
  return { token, expiresAt };
}

export async function getSessionFromToken(token: string | undefined): Promise<AuthSession | null> {
  if (!token) return null;
  const session = await prisma.userSession.findUnique({
    where: { tokenHash: hashSessionToken(token) },
    include: {
      user: {
        include: {
          roles: { include: { role: true } },
          participantProfile: { select: { id: true } }
        }
      }
    }
  });
  if (!session || session.revokedAt || session.expiresAt <= new Date()) return null;
  if (session.user.accountStatus !== "ACTIVE") return null;
  return {
    id: session.user.id,
    email: session.user.email,
    roles: session.user.roles.map((userRole) => userRole.role.code),
    participantProfileId: session.user.participantProfile?.id ?? null
  };
}

export async function getSessionFromRequest(request: Request) {
  const token = request.headers.get("cookie")?.split(";").map((part) => part.trim()).find((part) => part.startsWith(`${sessionCookieName}=`))?.split("=")[1];
  return getSessionFromToken(token ? decodeURIComponent(token) : undefined);
}

export async function getAdminSessionFromRequest(request: Request) {
  const session = await getSessionFromRequest(request);
  if (!session || !session.roles.some(isAdminRole)) return null;
  return session;
}

export async function getCurrentSession() {
  const cookieStore = await cookies();
  return getSessionFromToken(cookieStore.get(sessionCookieName)?.value);
}

export async function requireSession(options?: { roles?: string[]; redirectTo?: string }) {
  const session = await getCurrentSession();
  if (!session) redirect(loginPathForRedirect(options?.redirectTo ?? "/"));
  if (options?.roles?.length && !session.roles.some((role) => options.roles?.includes(role))) {
    redirect(userHomePath(session.roles));
  }
  return session;
}

export async function requireAdminSession(redirectTo = "/admin") {
  const session = await getCurrentSession();
  if (!session) redirect(loginPathForRedirect(redirectTo));
  if (!session.roles.some(isAdminRole)) redirect(userHomePath(session.roles));
  return session;
}

export async function requireParticipantSession(redirectTo = "/participant") {
  const session = await getCurrentSession();
  if (!session) redirect(loginPathForRedirect(redirectTo));
  if (!session.roles.includes("PARTICIPANT")) redirect(userHomePath(session.roles));
  return session;
}
