import { and, eq, gt, isNull } from "drizzle-orm";
import type { AuthActorType, StaffRole } from "../@types/auth.js";
import { db } from "../db/index.js";
import { authSessions } from "../db/schema.js";
import {
  generateRefreshToken,
  hashRefreshToken,
  refreshTokenExpiryDate,
  signAccessToken,
} from "./authSecurity.js";

export const createAuthSession = async ({
  actorType,
  actorId,
  role,
  userAgent,
  ipAddress,
}: {
  actorType: AuthActorType;
  actorId: number;
  role?: StaffRole;
  userAgent?: string;
  ipAddress?: string;
}) => {
  const refreshToken = generateRefreshToken();
  const refreshTokenHash = hashRefreshToken(refreshToken);

  await db.insert(authSessions).values({
    actorType,
    actorId,
    refreshTokenHash,
    userAgent,
    ipAddress,
    expiresAt: refreshTokenExpiryDate(),
    lastUsedAt: new Date(),
  });

  const accessToken = signAccessToken({
    sub: actorId.toString(),
    actorType,
    role,
  });

  return { accessToken, refreshToken };
};

export const findValidRefreshSession = async (refreshToken: string) => {
  const refreshTokenHash = hashRefreshToken(refreshToken);

  const [session] = await db
    .select()
    .from(authSessions)
    .where(
      and(
        eq(authSessions.refreshTokenHash, refreshTokenHash),
        isNull(authSessions.revokedAt),
        gt(authSessions.expiresAt, new Date()),
      ),
    )
    .limit(1);

  return session ?? null;
};

export const revokeRefreshSession = async (refreshToken: string) => {
  await db
    .update(authSessions)
    .set({ revokedAt: new Date() })
    .where(eq(authSessions.refreshTokenHash, hashRefreshToken(refreshToken)));
};

export const revokeAllActorSessions = async (actorType: AuthActorType, actorId: number) => {
  await db
    .update(authSessions)
    .set({ revokedAt: new Date() })
    .where(
      and(
        eq(authSessions.actorType, actorType),
        eq(authSessions.actorId, actorId),
        isNull(authSessions.revokedAt),
      ),
    );
};

export const rotateRefreshSession = async ({
  oldRefreshToken,
  actorType,
  actorId,
  role,
  userAgent,
  ipAddress,
}: {
  oldRefreshToken: string;
  actorType: AuthActorType;
  actorId: number;
  role?: StaffRole;
  userAgent?: string;
  ipAddress?: string;
}) => {
  await revokeRefreshSession(oldRefreshToken);
  return createAuthSession({ actorType, actorId, role, userAgent, ipAddress });
};
