import { Middleware } from "rjweb-server";
import { type User } from "@prisma/client";
import { prisma } from "../db";
import { createLogger } from "../logger";
import { ERROR_MESSAGES } from "../errors";

const log = createLogger("AUTH");

const COOKIE_NAME = "pp_session";

export type AuthState =
  | { success: true; user: User; sessionId: number }
  | { success: false; message: string; tokenProvided: boolean };

type AuthContext = {
  auth?: AuthState;
};

export const authResolutionMiddleware = new Middleware<{}, AuthContext>(
  "Auth Resolution Middleware",
  "1.0.0",
)
  .load(() => {
    log.info("Auth resolution middleware loaded");
  })
  .httpRequest(async (_config, _server, context, ctr) => {
    const cookieToken = ctr.cookies.get(COOKIE_NAME);
    const tokenProvided = Boolean(cookieToken);
    const data = context.data(authResolutionMiddleware);

    if (!cookieToken) {
      data.auth = { success: false, message: "No session", tokenProvided: false };
      return;
    }

    const session = await prisma.session.findFirst({
      where: { hash: cookieToken },
      include: { user: true },
    });

    if (!session) {
      data.auth = { success: false, message: "Invalid session", tokenProvided };
      return;
    }

    data.auth = { success: true, user: session.user, sessionId: session.id };
  })
  .httpRequestContext(
    (_config, Original) =>
      class extends Original {
        getAuth(): AuthState {
          const data = this.context.data(authResolutionMiddleware);
          if (!data.auth) {
            return { success: false, message: "Auth not resolved", tokenProvided: false };
          }
          return data.auth;
        }
      },
  )
  .export();

export const authEnforcementMiddleware = new Middleware<{}, {}>(
  "Auth Enforcement Middleware",
  "1.0.0",
)
  .httpRequest(async (_config, _server, context, ctr, end) => {
    const data = context.data(authResolutionMiddleware) as AuthContext;
    const auth = data.auth;

    if (!auth || auth.success || !auth.tokenProvided) {
      return;
    }

    return end(
      ctr.status(ERROR_MESSAGES.UNAUTHORIZED.code).print({
        status: "FAILED",
        message: auth.message,
      }),
    );
  })
  .export();

export const COOKIE_NAME_EXPORT = COOKIE_NAME;

// The auth-resolution middleware only runs on HTTP requests (`.httpRequest` /
// `.httpRequestContext`), so `ctr.getAuth()` is never populated on a WebSocket
// upgrade — every WS would see `success: false` and close 1008. WebSocket
// handlers must resolve the session themselves; this mirrors the middleware's
// logic. Reads the session cookie via the ctr cookie API, falling back to
// parsing the raw `Cookie` header from the upgrade request.
export async function resolveWsAuth(ctr: any): Promise<AuthState> {
  let cookieToken: string | undefined;
  try {
    cookieToken = ctr.cookies?.get?.(COOKIE_NAME);
  } catch {
    // fall through to header parsing
  }
  if (!cookieToken) {
    const raw = (ctr.headers?.get?.("cookie") as string | undefined) || "";
    const match = raw.match(/(?:^|;\s*)pp_session=([^;]+)/);
    if (match) cookieToken = decodeURIComponent(match[1]);
  }

  if (!cookieToken) {
    return { success: false, message: "No session", tokenProvided: false };
  }

  const session = await prisma.session.findFirst({
    where: { hash: cookieToken },
    include: { user: true },
  });

  if (!session) {
    return { success: false, message: "Invalid session", tokenProvided: true };
  }

  return { success: true, user: session.user, sessionId: session.id };
}
