import { NextResponse } from "next/server";
import Stripe from "stripe";
import { deleteUserAccount } from "@/lib/auth/accounts";
import { normaliseEmail } from "@/lib/auth/email";
import { requireCsrf } from "@/lib/auth/csrf";
import { authError, rateLimited, requireDatabase } from "@/lib/auth/http";
import { clientIp, rateLimit } from "@/lib/auth/rateLimit";
import {
  logSecurityEventAsync,
  SecurityEventType,
} from "@/lib/auth/securityEvents";
import { destroySession, getCurrentUser } from "@/lib/auth/session";
import { MAX_JSON_BODY_BYTES } from "@/lib/limits";
import { readJsonSized } from "@/lib/http/requestSize";

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

const stripe = process.env.STRIPE_SECRET_KEY
  ? new Stripe(process.env.STRIPE_SECRET_KEY, {
      apiVersion: "2025-01-27.acacia" as any,
      maxNetworkRetries: 2,
    })
  : null;

interface DeleteAccountBody {
  password?: string;
  confirmEmail?: string;
}

/**
 * Permanently deletes the signed-in account.
 *
 * Two independent proofs are required before anything is destroyed: the exact
 * account email typed back (catches a misclick on an irreversible action) and,
 * for accounts with a local password, that password (a stolen session cookie
 * alone must not be enough — same rule as `/api/auth/change-password`).
 *
 * Order matters: verification happens first via `deleteUserAccount`, which
 * only deletes on success. Only once the row is actually gone do we cancel any
 * live Stripe subscription and clear the session cookie — a failed attempt
 * must leave billing and the session untouched.
 */
export async function DELETE(request: Request) {
  const csrfBlocked = requireCsrf(request);
  if (csrfBlocked) return csrfBlocked;

  const dbDown = await requireDatabase();
  if (dbDown) return dbDown;

  const user = await getCurrentUser(request);
  if (!user) return authError("unauthorized", 401);

  if (user.isGuest) return authError("invalid_request", 400);

  // Destructive and irreversible — a tighter budget than a mere password
  // change. Keyed by both IP and account so neither a shared network nor a
  // single attacker session gets many guesses at the password.
  const ip = clientIp(request);
  const ipLimit = rateLimit(`delete:ip:${ip}`, 5, 15 * 60 * 1000);
  if (!ipLimit.allowed) return rateLimited(ipLimit.retryAfter);
  const userLimit = rateLimit(`delete:user:${user.id}`, 5, 15 * 60 * 1000);
  if (!userLimit.allowed) return rateLimited(userLimit.retryAfter);

  const parsed = await readJsonSized<DeleteAccountBody>(request, MAX_JSON_BODY_BYTES);
  if (!parsed.ok) {
    return parsed.response.status === 413 ? parsed.response : authError("invalid_request", 400);
  }
  const body = parsed.body;
  if (!body?.confirmEmail) return authError("invalid_request", 400);

  const typedKey = normaliseEmail(body.confirmEmail);
  const accountKey = user.email ? normaliseEmail(user.email) : null;
  if (!typedKey || !accountKey || typedKey !== accountKey) {
    return authError("confirmation_mismatch", 400);
  }

  try {
    const outcome = await deleteUserAccount(user.id, body.password ?? null);

    if (outcome.status === "invalid") return authError("unauthorized", 401);
    if (outcome.status === "wrong_password") {
      logSecurityEventAsync({
        type: SecurityEventType.LOGIN_FAILED,
        userId: user.id,
        email: user.email,
        ip,
        userAgent: request.headers.get("user-agent"),
        metadata: { route: "/api/auth/delete-account", reason: "wrong_password" },
      });
      return authError("wrong_password", 400);
    }

    // The row is gone at this point — everything below is best-effort cleanup
    // using the user snapshot captured before deletion.
    if (user.stripeSubscriptionId && stripe) {
      try {
        await stripe.subscriptions.cancel(user.stripeSubscriptionId);
      } catch (stripeErr: any) {
        console.warn(
          "[auth] could not cancel Stripe subscription during account deletion (may already be cancelled):",
          stripeErr?.message
        );
      }
    }

    logSecurityEventAsync({
      type: SecurityEventType.ACCOUNT_DELETED,
      // The user row no longer exists, so this event carries no FK — the
      // email alone keeps it useful for the audit trail.
      userId: null,
      email: user.email,
      ip,
      userAgent: request.headers.get("user-agent"),
      metadata: { route: "/api/auth/delete-account", hadSubscription: Boolean(user.stripeSubscriptionId) },
    });

    await destroySession();

    return NextResponse.json({ status: "account_deleted" });
  } catch (error) {
    console.error("[auth] account deletion failed", error);
    return authError("server_error", 500);
  }
}
