import crypto from "node:crypto";
import type { NextRequest } from "next/server";
import { Role } from "@prisma/client";
import { prisma } from "@/server/db";
import { UnauthorizedError } from "@/server/errors";
import { signAccessToken, signRefreshToken, verifyAccessToken } from "@/server/security/jwt";

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

export function clientIp(request: NextRequest) {
  return request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? request.headers.get("x-real-ip") ?? "unknown";
}

export async function createSessionTokens(input: {
  userId: string;
  roles: Role[];
  ipAddress?: string;
  userAgent?: string;
}) {
  const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
  const session = await prisma.session.create({
    data: {
      userId: input.userId,
      refreshTokenHash: "pending",
      ipAddress: input.ipAddress,
      userAgent: input.userAgent,
      expiresAt
    }
  });
  const refreshToken = await signRefreshToken({ userId: input.userId, sessionId: session.id });
  const accessToken = await signAccessToken({ userId: input.userId, sessionId: session.id, roles: input.roles });

  await prisma.session.update({
    where: { id: session.id },
    data: { refreshTokenHash: hashOpaqueToken(refreshToken) }
  });

  return { accessToken, refreshToken, sessionId: session.id, expiresAt };
}

export async function requireUser(request: NextRequest) {
  const header = request.headers.get("authorization");
  const token = header?.startsWith("Bearer ")
    ? header.slice("Bearer ".length)
    : request.cookies.get("access_token")?.value;

  if (!token) {
    throw new UnauthorizedError();
  }

  const claims = await verifyAccessToken(token);
  const session = await prisma.session.findFirst({
    where: {
      id: claims.sessionId,
      userId: claims.sub,
      revokedAt: null,
      expiresAt: { gt: new Date() }
    }
  });

  if (!session) {
    throw new UnauthorizedError("Session expired");
  }

  const user = await prisma.user.findUnique({
    where: { id: claims.sub },
    include: { profile: true }
  });

  if (!user) {
    throw new UnauthorizedError();
  }

  return { user, session, roles: claims.roles };
}
