/**
 * CSRF Protection System
 *
 * Protects state-changing operations from Cross-Site Request Forgery attacks
 * Implements double-submit cookie pattern with secure token generation
 */

import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";

const CSRF_TOKEN_LENGTH = 32;
const CSRF_COOKIE_NAME = "csrf-token";
const CSRF_HEADER_NAME = "x-csrf-token";

/**
 * Generate a cryptographically secure CSRF token
 */
export function generateCSRFToken(): string {
  return crypto.randomBytes(CSRF_TOKEN_LENGTH).toString("hex");
}

/**
 * Validate CSRF token from request
 * Implements double-submit cookie pattern
 */
export function validateCSRFToken(request: NextRequest): boolean {
  // Get token from header
  const headerToken = request.headers.get(CSRF_HEADER_NAME);

  // Get token from cookie
  const cookieToken = request.cookies.get(CSRF_COOKIE_NAME)?.value;

  // Development logging
  if (process.env.NODE_ENV !== "production") {
    console.log("[CSRF] Validation:", {
      hasHeader: !!headerToken,
      hasCookie: !!cookieToken,
      headerLength: headerToken?.length || 0,
      cookieLength: cookieToken?.length || 0,
      match: headerToken === cookieToken,
    });
  }

  // Both must exist and match
  if (!headerToken || !cookieToken) {
    if (process.env.NODE_ENV !== "production") {
      console.error("[CSRF] ❌ Missing token:", {
        header: headerToken ? "present" : "MISSING",
        cookie: cookieToken ? "present" : "MISSING",
      });
    }
    return false;
  }

  // Timing-safe comparison to prevent timing attacks
  try {
    const isValid = crypto.timingSafeEqual(
      Buffer.from(headerToken),
      Buffer.from(cookieToken),
    );

    if (!isValid && process.env.NODE_ENV !== "production") {
      console.error("[CSRF] ❌ Token mismatch");
    }

    return isValid;
  } catch (error) {
    if (process.env.NODE_ENV !== "production") {
      console.error("[CSRF] ❌ Validation error:", error);
    }
    return false;
  }
}

/**
 * Middleware wrapper to require CSRF token validation
 * Only applies to state-changing methods (POST, PUT, DELETE, PATCH)
 *
 * Usage:
 * export const POST = withCSRF(handler);
 */
export function withCSRF<
  T extends (request: NextRequest, ...args: never[]) => Promise<NextResponse>,
>(handler: T): T {
  const wrapped = async (
    request: NextRequest,
    ...rest: unknown[]
  ): Promise<NextResponse> => {
    // Only validate CSRF for state-changing methods
    if (["POST", "PUT", "DELETE", "PATCH"].includes(request.method)) {
      if (!validateCSRFToken(request)) {
        return NextResponse.json(
          { error: "Invalid or missing CSRF token" },
          { status: 403 },
        );
      }
    }

    // CSRF validation passed, execute handler with all arguments (including context)
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    return (handler as (...a: unknown[]) => Promise<NextResponse>)(
      request,
      ...rest,
    );
  };
  return wrapped as unknown as T;
}

/**
 * Set CSRF token in response cookie
 *
 * Call this when creating a new session or on login
 */
export function setCSRFCookie(response: NextResponse, token: string): void {
  response.cookies.set(CSRF_COOKIE_NAME, token, {
    httpOnly: false, // Must be readable by JavaScript to include in request headers
    secure: process.env.NODE_ENV === "production", // Only send over HTTPS in production
    sameSite: "lax", // Changed from 'strict' to allow form submissions
    path: "/",
    maxAge: 60 * 60 * 2, // 2 hours
  });
}

/**
 * Helper to create a response with CSRF token
 */
export function createResponseWithCSRF(
  data: unknown,
  options?: ResponseInit,
): NextResponse {
  const token = generateCSRFToken();
  const response = NextResponse.json(data, options);
  setCSRFCookie(response, token);
  return response;
}
