import type { Context, Middleware } from "@wrnexus/core";
import type { AuthEngine } from "./engine.ts";
import { publicUser } from "./normalize.ts";
import type { AuthenticatedContext, AuthPublicUser, AuthSession } from "./types.ts";

export const AUTH_SESSION_KEY = "wrnexus.auth.session";

function wantsJson(ctx: Context): boolean {
  if (ctx.url.pathname.startsWith("/api/") || ctx.url.pathname.startsWith("/__wrnexus/"))
    return true;
  const accept = ctx.req.headers.get("accept") ?? "";
  return accept.includes("application/json") && !accept.includes("text/html");
}

export interface AuthSessionOptions {
  /** Optional shared SSO cookie containing an AuthEngine session id. */
  cookieName?: string;
}

export function authSession(engine: AuthEngine, options: AuthSessionOptions = {}): Middleware {
  return async (ctx, next) => {
    const sessionId =
      (options.cookieName ? ctx.cookies.get(options.cookieName) : undefined) ??
      ctx.session.get<string>(AUTH_SESSION_KEY);
    if (!sessionId) {
      ctx.user = null;
      ctx.locals.authUser = null;
      delete ctx.locals.authSession;
      return next();
    }
    const session = await engine.validateSession(sessionId);
    if (!session) {
      ctx.session.delete(AUTH_SESSION_KEY);
      ctx.user = null;
      ctx.locals.authUser = null;
      delete ctx.locals.authSession;
      return next();
    }
    const user = await engine.getUser(session.userId);
    if (!user || user.status !== "active") {
      ctx.session.delete(AUTH_SESSION_KEY);
      ctx.user = null;
      ctx.locals.authUser = null;
      delete ctx.locals.authSession;
      return next();
    }
    const safe = publicUser(user);
    ctx.user = safe;
    ctx.locals.authUser = safe;
    ctx.locals.authSession = session;
    return next();
  };
}

export function establishAuthSession(
  ctx: Context,
  session: AuthSession,
  user: AuthPublicUser,
): void {
  ctx.session.regenerate();
  ctx.session.set(AUTH_SESSION_KEY, session.id);
  ctx.user = user;
  ctx.locals.authUser = user;
  ctx.locals.authSession = session;
}

export function clearAuthSession(ctx: Context): void {
  ctx.session.delete(AUTH_SESSION_KEY);
  ctx.user = null;
  delete ctx.locals.authUser;
  delete ctx.locals.authSession;
}

export function getAuthUser(ctx: Context): AuthPublicUser | null {
  return (
    (ctx.locals.authUser as AuthPublicUser | null | undefined) ??
    (ctx.user as AuthPublicUser | null | undefined) ??
    null
  );
}

export function getAuthSession(ctx: Context): AuthSession | null {
  return (ctx.locals.authSession as AuthSession | undefined) ?? null;
}

export interface RequireAuthOptions {
  loginPath?: string;
  returnToParam?: string;
  roles?: string[];
  status?: AuthPublicUser["status"][];
}

export function requireAuth(options: RequireAuthOptions = {}): Middleware {
  const loginPath = options.loginPath ?? "/sign-in";
  const returnToParam = options.returnToParam ?? "returnTo";
  return (ctx, next) => {
    const user = getAuthUser(ctx);
    const allowedStatus = options.status ?? ["active"];
    const allowedRole =
      !options.roles?.length || options.roles.some((role) => user?.roles.includes(role));
    if (user && allowedStatus.includes(user.status) && allowedRole) return next();
    if (wantsJson(ctx)) {
      return Response.json(
        { ok: false, error: user ? "Forbidden" : "Unauthorized" },
        { status: user ? 403 : 401 },
      );
    }
    const redirect = new URL(loginPath, ctx.url);
    redirect.searchParams.set(returnToParam, `${ctx.url.pathname}${ctx.url.search}`);
    return Response.redirect(redirect, 302);
  };
}

export function isAuthenticatedContext(ctx: Context): ctx is AuthenticatedContext {
  return Boolean(getAuthUser(ctx));
}
