import { NextResponse } from "next/server";
import { resetPasswordWithToken } from "@/lib/auth/accounts";
import { clientIp, rateLimit } from "@/lib/auth/rateLimit";
import { authError, rateLimited, requireDatabase } from "@/lib/auth/http";
import { requireCsrf } from "@/lib/auth/csrf";
import { validatePassword } from "@/lib/auth/validation";
import {
  logSecurityEventAsync,
  SecurityEventType,
} from "@/lib/auth/securityEvents";
import { db } from "@/lib/db";
import { users } from "@/lib/schema/db";
import { eq } from "drizzle-orm";
import { MAX_JSON_BODY_BYTES } from "@/lib/limits";
import { readJsonSized } from "@/lib/http/requestSize";

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

interface ResetBody {
  token?: string;
  password?: string;
}

/**
 * Completes a password reset. No session is issued: the caller proves nothing
 * beyond inbox control, and every existing session for the account is dropped by
 * `resetPasswordWithToken`, so the user signs in again with the new password.
 */
export async function POST(request: Request) {
  const dbDown = await requireDatabase();
  if (dbDown) return dbDown;

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

  const parsed = await readJsonSized<ResetBody>(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?.token || !body?.password) return authError("invalid_request", 400);

  // Guessing a 32-byte token is hopeless, but rate limiting keeps the endpoint
  // from being a free scrypt-hashing service. Two windows: a short burst cap so
  // a single scripted run cannot pound the scrypt cost in quick succession, and
  // the hourly budget from before so a normal reset flow is never affected.
  const ip = clientIp(request);
  const burstLimit = rateLimit(`reset:burst:ip:${ip}`, 5, 10 * 60 * 1000);
  if (!burstLimit.allowed) return rateLimited(burstLimit.retryAfter);

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

  // Server-side re-validation: the client checks are UX, never a control.
  if (validatePassword(body.password, false, { requireStrength: true })) {
    return authError("weak_password", 400);
  }

  try {
    const outcome = await resetPasswordWithToken(body.token, body.password);

    if (outcome.status === "expired") return authError("expired_token", 410);
    if (outcome.status === "invalid") return authError("invalid_token", 400);

    // Success: recover the account email for the audit trail. The lookup is
    // best-effort — a failure must never turn a successful reset into a 500.
    const owner = await db
      .select({ email: users.email })
      .from(users)
      .where(eq(users.id, outcome.userId))
      .limit(1)
      .catch(() => []);

    logSecurityEventAsync({
      type: SecurityEventType.PASSWORD_RESET_COMPLETED,
      userId: outcome.userId,
      email: owner[0]?.email ?? null,
      ip,
      userAgent: request.headers.get("user-agent"),
      metadata: { route: "/api/auth/reset-password" },
    });

    return NextResponse.json({ status: "password_reset" });
  } catch (error) {
    console.error("[auth] password reset failed", error);
    return authError("server_error", 500);
  }
}
