"use server";

import { redirect } from "next/navigation";
import { z } from "zod";

import { verifyAgainstDummy, verifyPassword } from "@/lib/auth/password";
import { createSession, destroySession } from "@/lib/auth/session";
import { db } from "@/lib/db";
import { rateLimit, resetRateLimit } from "@/lib/rate-limit";
import { requestClientIp } from "@/lib/request";

export type LoginState = {
  error?: string;
  email?: string;
};

const loginSchema = z.object({
  email: z.string().trim().toLowerCase().email().max(254),
  password: z.string().min(1).max(256),
  next: z.string().max(2000).optional(),
});

const WINDOW_MS = 15 * 60 * 1000;

/** Only same-site admin paths, so `?next=` can't become an open redirect. */
function safeRedirectPath(next: string | undefined) {
  if (!next || !next.startsWith("/admin") || next.startsWith("//")) {
    return "/admin";
  }
  return next;
}

export async function login(
  _previous: LoginState,
  formData: FormData,
): Promise<LoginState> {
  const parsed = loginSchema.safeParse({
    email: formData.get("email"),
    password: formData.get("password"),
    next: formData.get("next") || undefined,
  });
  const email = String(formData.get("email") ?? "").slice(0, 254);
  if (!parsed.success) {
    return { error: "Enter a valid email and password.", email };
  }

  const ip = await requestClientIp();
  const accountKey = `login:account:${parsed.data.email}`;
  const [byIp, byAccount] = await Promise.all([
    rateLimit(`login:ip:${ip}`, { limit: 30, windowMs: WINDOW_MS }),
    rateLimit(accountKey, { limit: 5, windowMs: WINDOW_MS }),
  ]);
  if (!byIp.allowed || !byAccount.allowed) {
    const minutes = Math.ceil(
      Math.max(byIp.retryAfterSeconds, byAccount.retryAfterSeconds) / 60,
    );
    return {
      error: `Too many sign-in attempts. Try again in ${minutes} minute${minutes === 1 ? "" : "s"}.`,
      email,
    };
  }

  const user = await db.adminUser.findUnique({
    where: { email: parsed.data.email },
    select: { id: true, passwordHash: true, isActive: true },
  });
  const passwordMatches = user
    ? await verifyPassword(parsed.data.password, user.passwordHash)
    : await verifyAgainstDummy(parsed.data.password);

  // One message for every failure, so the form doesn't reveal which accounts exist.
  if (!user || !passwordMatches || !user.isActive) {
    return { error: "Incorrect email or password.", email };
  }

  await resetRateLimit(accountKey);
  await db.adminUser.update({
    where: { id: user.id },
    data: { lastLoginAt: new Date() },
  });
  await createSession(user.id);

  redirect(safeRedirectPath(parsed.data.next));
}

export async function logout() {
  await destroySession();
  redirect("/login");
}
