import { NextResponse } from "next/server";
import { findUserByEmail, issuePasswordResetLink } from "@/lib/auth/accounts";
import { normaliseEmail } from "@/lib/auth/email";
import { equaliseTiming } from "@/lib/auth/password";
import { clientIp, rateLimit } from "@/lib/auth/rateLimit";
import { hashIp } from "@/lib/auth/tokens";
import { authError, rateLimited, requireDatabase } from "@/lib/auth/http";
import { requireCsrf } from "@/lib/auth/csrf";
import { isProduction, smtp } from "@/lib/auth/config";
import {
  logSecurityEventAsync,
  SecurityEventType,
} from "@/lib/auth/securityEvents";
import { MAX_JSON_BODY_BYTES } from "@/lib/limits";
import { readJsonSized } from "@/lib/http/requestSize";

export const runtime = "nodejs";
export const dynamic = "force-dynamic";

interface ForgotBody {
  email?: string;
  lang?: "de" | "en";
}

/**
 * Starts a password reset.
 *
 * Always answers "sent", whether or not the address is registered. This endpoint
 * needs no credentials, so a truthful answer would turn it into a free
 * account-existence oracle — and into a way to spray mail at arbitrary inboxes.
 */
export async function POST(request: Request) {
  const dbDown = await requireDatabase();
  if (dbDown) return dbDown;

  const csrfBlocked = requireCsrf(request);
  if (csrfBlocked) return csrfBlocked;

  if (isProduction && !smtp.isConfigured) {
    console.error("[auth] password reset blocked — SMTP is not configured in production");
    return authError("mail_failed", 503);
  }

  const parsed = await readJsonSized<ForgotBody>(request, MAX_JSON_BODY_BYTES);
  if (!parsed.ok) {
    // Oversized bodies are rejected with 413 before anything is buffered;
    // parse failures keep the existing invalid_request/400 contract.
    return parsed.response.status === 413
      ? parsed.response
      : authError("invalid_request", 400);
  }
  const body = parsed.body;
  if (!body?.email) return authError("invalid_request", 400);

  const ip = clientIp(request);
  const ipLimit = rateLimit(`forgot:ip:${ip}`, 5, 60 * 60 * 1000);
  if (!ipLimit.allowed) return rateLimited(ipLimit.retryAfter);

  const emailKey = normaliseEmail(body.email);
  if (!emailKey) return NextResponse.json({ status: "reset_sent" });

  const emailLimit = rateLimit(`forgot:email:${emailKey}`, 3, 60 * 60 * 1000);
  if (!emailLimit.allowed) return rateLimited(emailLimit.retryAfter);

  const lang = body.lang === "de" ? "de" : "en";

  try {
    const user = await findUserByEmail(body.email);
    if (!user || !user.email) {
      // Missing account: same shape, and burn one scrypt's worth of time so a
      // registered and an unregistered address cannot be told apart by the
      // response time either. The audit trail still records the request so
      // reset-spraying against unregistered addresses is visible.
      await equaliseTiming();
      logSecurityEventAsync({
        type: SecurityEventType.PASSWORD_RESET_REQUESTED,
        email: emailKey,
        ip,
        userAgent: request.headers.get("user-agent"),
        metadata: { reason: "account_not_found", route: "/api/auth/forgot-password" },
      });
      return NextResponse.json({ status: "reset_sent" });
    }

    const mail = await issuePasswordResetLink(user, lang, hashIp(ip));
    logSecurityEventAsync({
      type: SecurityEventType.PASSWORD_RESET_REQUESTED,
      userId: user.id,
      email: user.email,
      ip,
      userAgent: request.headers.get("user-agent"),
      metadata: { route: "/api/auth/forgot-password" },
    });
    return NextResponse.json({ status: "reset_sent", devLink: mail.devLink });
  } catch (error) {
    console.error("[auth] password reset request failed", error);
    return authError("mail_failed", 500);
  }
}
