import { SignJWT, jwtVerify } from "jose";
import { cookies } from "next/headers";
import type { NextRequest } from "next/server";
import { prisma } from "@/lib/prisma";
import { MEMBER_ROLES, STAFF_SIDE_ROLES } from "@/lib/constants";

export const SESSION_COOKIE = "ems_session";

export type Role =
  | "SUPER_ADMIN"
  | "INSTITUTION_ADMIN"
  | "STUDENT"
  | "TEACHER"
  | "PARENT"
  | "STAFF"
  | "PRINCIPAL"
  | "VICE_PRINCIPAL"
  | "EXECUTIVE_MEMBER";

/** Member roles — they log in with institution code + username. */
export { MEMBER_ROLES, STAFF_SIDE_ROLES };
export type MemberRole = (typeof MEMBER_ROLES)[number];
export type StaffSideRole = (typeof STAFF_SIDE_ROLES)[number];

export function isMemberRole(role: string): role is MemberRole {
  return (MEMBER_ROLES as readonly string[]).includes(role);
}

export function isStaffSideRole(role: string): role is StaffSideRole {
  return (STAFF_SIDE_ROLES as readonly string[]).includes(role);
}

/** Landing page per role after login / when bouncing from a wrong area. */
export function homeForRole(role: string): string {
  if (role === "SUPER_ADMIN") return "/central";
  if (role === "INSTITUTION_ADMIN") return "/portal";
  return "/me";
}

export interface SessionPayload {
  userId: number;
  role: Role;
  name: string;
  email: string | null;
  username?: string | null;
  institutionId: number | null;
  /// epoch seconds — set only for demo-environment logins; the session is
  /// rejected once the granted demo window is over
  demoExpiresAt?: number;
}

/** Browsers may keep the login state for at most 3 hours. */
export const SESSION_MAX_AGE_SECONDS = 60 * 60 * 3;

function getSecret() {
  return new TextEncoder().encode(process.env.JWT_SECRET ?? "dev-secret");
}

export async function signSession(payload: SessionPayload): Promise<string> {
  return new SignJWT({ ...payload })
    .setProtectedHeader({ alg: "HS256" })
    .setIssuedAt()
    .setExpirationTime(SESSION_MAX_AGE_SECONDS + "s")
    .sign(getSecret());
}

export async function verifySessionToken(token: string): Promise<SessionPayload | null> {
  try {
    const { payload } = await jwtVerify(token, getSecret());
    if (typeof payload.userId !== "number") return null;
    // An expired demo window invalidates the session immediately — even
    // before the short-lived cookie itself runs out.
    if (
      typeof payload.demoExpiresAt === "number" &&
      Date.now() / 1000 >= payload.demoExpiresAt
    ) {
      return null;
    }
    return payload as unknown as SessionPayload;
  } catch {
    return null;
  }
}

/** Read session from the request cookies (API route handlers). */
export async function getSession(): Promise<SessionPayload | null> {
  const store = await cookies();
  const token = store.get(SESSION_COOKIE)?.value;
  if (!token) return null;
  return verifySessionToken(token);
}

/** Read session directly from a NextRequest (middleware). */
export async function getRequestSession(req: NextRequest): Promise<SessionPayload | null> {
  const token = req.cookies.get(SESSION_COOKIE)?.value;
  if (!token) return null;
  return verifySessionToken(token);
}

export async function requireRole(role: Role): Promise<SessionPayload | null> {
  const session = await getSession();
  if (!session || session.role !== role) return null;
  return session;
}

/** Require any of the given roles (e.g. the four member roles for /api/me). */
export async function requireAnyRole(roles: readonly Role[]): Promise<SessionPayload | null> {
  const session = await getSession();
  if (!session || !roles.includes(session.role)) return null;
  return session;
}

/** Campus (College & University) module access — an institution admin whose
    institution is a college/university type. Other institution types
    (primary/high school, coaching, madrasha) get null. */
export async function requireCampusInstitution(): Promise<SessionPayload | null> {
  const session = await requireRole("INSTITUTION_ADMIN");
  if (!session || !session.institutionId) return null;
  const inst = await prisma.institution.findUnique({
    where: { id: session.institutionId },
    select: { type: true },
  });
  if (!inst || inst.type !== "COLLEGE_AND_UNIVERSITY") return null;
  return session;
}
