/**
 * Reset Password Endpoint
 *
 * AUTH STRATEGY: Clerk-first with legacy OTP fallback.
 * When Clerk is configured, password resets are handled by Clerk's prebuilt
 * <SignIn> component (forgot-password flow). This endpoint is only called
 * by the legacy OTP-based flow when Clerk is NOT configured.
 * If a request arrives while Clerk is active, we return a redirect hint
 * rather than processing it, as a safety net.
 *
 * Security features:
 * - Rate limiting (5 requests per hour)
 * - Input validation and sanitization
 * - Strong password requirements
 * - Reset token validation (prevents unauthorized resets)
 *
 * Note: CSRF protection not needed for unauthenticated password reset flows
 */

import { NextRequest, NextResponse } from "next/server";
import bcrypt from "bcryptjs";
import { withRateLimit, rateLimiters } from "@/lib/rate-limiter";
import {
  validateEmail,
  validatePassword,
  sanitizeInput,
} from "@/lib/input-validation";
import { logger } from "@/lib/logger";
import { prisma } from "@/lib/db";
import { getDataSource } from "@/lib/data-source";

const isClerkConfigured = () =>
  Boolean(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY);

// Force dynamic rendering to prevent build-time execution
export const dynamic = "force-dynamic";
export const runtime = "nodejs";

async function resetPasswordHandler(request: NextRequest) {
  try {
    // ── Clerk-first path ──
    // When Clerk is the primary auth system, password resets are handled
    // by Clerk's built-in UI. If a request reaches this endpoint anyway,
    // return a redirect hint so the client navigates to the Clerk flow.
    if (isClerkConfigured()) {
      logger.info("Reset password request received while Clerk is active — redirecting to Clerk flow");
      return NextResponse.json(
        {
          success: false,
          useClerk: true,
          message: "Password reset is handled by the sign-in page. Please use the forgot password link there.",
        },
        { status: 200 },
      );
    }

    const body = await request.json();
    const { identifier, newPassword, resetToken } = body;

    if (!identifier || !newPassword) {
      return NextResponse.json(
        { error: "Identifier and new password are required" },
        { status: 400 },
      );
    }

    const sanitizedIdentifier = sanitizeInput(identifier);

    // Validate password strength (12+ chars with complexity)
    const passwordValidation = validatePassword(newPassword);
    if (!passwordValidation.valid) {
      return NextResponse.json(
        {
          error:
            passwordValidation.error || "Password does not meet requirements",
        },
        { status: 400 },
      );
    }

    // Detect if identifier is email or phone
    const emailValidation = validateEmail(sanitizedIdentifier);
    const isEmail = emailValidation.valid;

    if (isEmail && !resetToken) {
      return NextResponse.json(
        { error: "Reset token is required for email-based reset" },
        { status: 400 },
      );
    }

    // Verify reset token (required for all resets after OTP verification)
    if (resetToken) {
      try {
        const decoded = JSON.parse(
          Buffer.from(resetToken, "base64").toString(),
        );

        // Verify identifier matches
        if (decoded.identifier !== sanitizedIdentifier) {
          logger.warn("Reset token identifier mismatch", {
            expected: decoded.identifier,
            received: sanitizedIdentifier,
          });
          return NextResponse.json(
            { error: "Invalid reset token" },
            { status: 401 },
          );
        }

        // Verify token hasn't expired (10 minutes)
        if (Date.now() > decoded.expires) {
          logger.warn("Reset token expired", {
            identifier: sanitizedIdentifier,
          });
          return NextResponse.json(
            { error: "Reset token has expired. Please request a new code." },
            { status: 401 },
          );
        }
      } catch (error) {
        logger.error("Reset token decode error", error);
        return NextResponse.json(
          { error: "Invalid reset token" },
          { status: 401 },
        );
      }
    } else {
      // Reset token is required to prevent unauthorized password changes
      return NextResponse.json(
        { error: "Reset token is required" },
        { status: 400 },
      );
    }

    // Hash the new password
    const hashedPassword = await bcrypt.hash(newPassword, 12);

    // Find user in database
    let user: {
      id: string;
      email?: string | null;
      phoneNumber?: string | null;
    } | null = null;

    // Convex data-source routing for user lookup (email only)
    if (isEmail && getDataSource("auth") === "convex") {
      try {
        const { convexQuery } = await import("@/lib/convex-client");
        const convexUser = await convexQuery("auth/queries:getUserByEmail", {
          email: sanitizedIdentifier.toLowerCase(),
        });
        if (convexUser) {
          const cu = convexUser as Record<string, unknown>;
          user = {
            id: (cu._id as string) || (cu.prismaId as string) || "",
            email: cu.email as string | null,
            phoneNumber: cu.phoneNumber as string | null,
          };
        }
      } catch (error) {
        console.error("[Convex fallback] reset-password lookup:", error);
        user = null;
      }
    }

    // Prisma fallback (or phone-based lookup)
    if (!user) {
      user = await prisma.users.findFirst({
        where: isEmail
          ? { email: sanitizedIdentifier.toLowerCase() }
          : { phoneNumber: sanitizedIdentifier },
      });
    }

    if (!user) {
      // Use generic error to prevent user enumeration
      logger.warn("Password reset attempt for non-existent user", {
        identifier: isEmail
          ? sanitizedIdentifier
          : sanitizedIdentifier.slice(-4),
      });
      return NextResponse.json(
        { error: "Password reset failed" },
        { status: 400 },
      );
    }

    // Update password in Prisma (Convex updateUserNoAuth does not support password field)
    // Look up by email to ensure we have the Prisma record for the update
    const prismaUser = await prisma.users.findFirst({
      where: isEmail
        ? { email: sanitizedIdentifier.toLowerCase() }
        : { phoneNumber: sanitizedIdentifier },
      select: { id: true },
    });

    if (prismaUser) {
      await prisma.users.update({
        where: { id: prismaUser.id },
        data: {
          password: hashedPassword,
          failedAttempts: 0, // Clear failed login attempts
          lockedUntil: null, // Clear any lockout
          updatedAt: new Date(),
        },
      });
    }

    // Log successful password reset
    await prisma.audit_logs.create({
      data: {
        id: crypto.randomUUID(), // Generate unique ID for audit log
        userId: user.id,
        type: "password_reset_success",
        action: "Password reset completed",
        success: true,
        ip:
          request.headers.get("x-forwarded-for") ||
          request.headers.get("x-real-ip") ||
          "127.0.0.1",
        userAgent: request.headers.get("user-agent") || undefined,
        metadata: { method: isEmail ? "email" : "phone" },
      },
    });

    logger.info("Password reset successful", {
      userId: user.id,
      method: isEmail ? "email" : "phone",
    });

    return NextResponse.json(
      {
        success: true,
        message: "Password reset successfully",
      },
      { status: 200 },
    );
  } catch (error) {
    logger.error("Reset password API error", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 },
    );
  }
}

// Apply rate limiting only (CSRF not needed for unauthenticated flows)
export const POST = withRateLimit(
  resetPasswordHandler,
  rateLimiters.authGeneral,
);
