import { NextRequest, NextResponse } from "next/server";
import { applyRateLimit, rateLimiters } from "./rate-limiter";
import { validateCSRFToken } from "./csrf";

type RateLimiter = Parameters<typeof applyRateLimit>[1];

/**
 * Simple rate limiter function for API routes
 * @deprecated Use withSecureRoute with rateLimiter option instead
 */
export async function rateLimiter(
  request: NextRequest,
  _options: { maxRequests?: number; windowMs?: number } = {},
): Promise<NextResponse | null> {
  // Use the general auth rate limiter as default
  // For more specific rate limiting, use withSecureRoute with rateLimiter option
  return applyRateLimit(request, rateLimiters.authGeneral);
}

const DEFAULT_JSON_BODY_LIMIT = 128 * 1024; // 128kb
const STATE_CHANGING_METHODS = ["POST", "PUT", "PATCH", "DELETE"];

function isStateChanging(method: string): boolean {
  return STATE_CHANGING_METHODS.includes(method.toUpperCase());
}

export async function parseJsonBody<T>(
  request: NextRequest,
  limit = DEFAULT_JSON_BODY_LIMIT,
): Promise<T> {
  const contentType = request.headers.get("content-type") || "";
  if (!contentType.toLowerCase().includes("application/json")) {
    throw new Error("UNSUPPORTED_CONTENT_TYPE");
  }

  const declaredLength = request.headers.get("content-length");
  if (declaredLength && Number(declaredLength) > limit) {
    throw new Error("PAYLOAD_TOO_LARGE");
  }

  const bodyText = await request.clone().text();
  const byteLength = new TextEncoder().encode(bodyText).length;

  if (byteLength > limit) {
    throw new Error("PAYLOAD_TOO_LARGE");
  }

  try {
    return JSON.parse(bodyText) as T;
  } catch {
    throw new Error("INVALID_JSON");
  }
}

type SecureRouteOptions<TBody> = {
  csrf?: boolean;
  parseJson?: boolean;
  bodyLimit?: number;
  rateLimiter?: RateLimiter;
  identifierFn?: (
    request: NextRequest,
  ) => Promise<string | undefined> | string | undefined;
  transformBody?: (body: unknown) => TBody;
};

export function withSecureRoute<TBody = unknown>(
  handler: (
    request: NextRequest,
    context: unknown,
    body: TBody | null,
  ) => Promise<NextResponse>,
  options: SecureRouteOptions<TBody> = {},
) {
  const {
    csrf = true,
    parseJson = true,
    bodyLimit = DEFAULT_JSON_BODY_LIMIT,
    rateLimiter,
    identifierFn,
    transformBody,
  } = options;

  return async (
    request: NextRequest,
    context: unknown,
  ): Promise<NextResponse> => {
    if (csrf && isStateChanging(request.method)) {
      const valid = validateCSRFToken(request);
      if (!valid) {
        return NextResponse.json(
          { error: "Invalid or missing CSRF token" },
          { status: 403 },
        );
      }
    }

    if (rateLimiter) {
      const identifier = identifierFn ? await identifierFn(request) : undefined;
      const limited = await applyRateLimit(request, rateLimiter, identifier);
      if (limited) {
        return limited;
      }
    }

    let parsedBody: TBody | null = null;
    const shouldParseJson =
      parseJson && request.method !== "GET" && request.method !== "HEAD";
    if (shouldParseJson) {
      try {
        const rawBody = await parseJsonBody<unknown>(request, bodyLimit);
        parsedBody = transformBody
          ? transformBody(rawBody)
          : (rawBody as TBody);
      } catch (error) {
        if (error instanceof Error) {
          if (error.message === "PAYLOAD_TOO_LARGE") {
            return NextResponse.json(
              { error: "Request body too large" },
              { status: 413 },
            );
          }

          if (error.message === "UNSUPPORTED_CONTENT_TYPE") {
            return NextResponse.json(
              { error: "Unsupported content type. Expected application/json" },
              { status: 415 },
            );
          }

          if (error.message === "INVALID_JSON") {
            return NextResponse.json(
              { error: "Invalid JSON payload" },
              { status: 400 },
            );
          }
        }

        return NextResponse.json(
          { error: "Unable to process request body" },
          { status: 400 },
        );
      }
    }

    return handler(request, context, parsedBody);
  };
}
