import { NextResponse } from 'next/server';
import { ZodSchema } from 'zod';
import { ValidationError } from '@server/errors';
import { RouteContext, RouteHandler } from './withErrorHandler';
import type { AuthedRequest } from './withAuth';

export type ParsedRequest<T> = Request & { parsedBody: T };
export type AuthedParsedRequest<T> = AuthedRequest & { parsedBody: T };

type ValidatedHandler<T> = (
  req: ParsedRequest<T>,
  context: RouteContext
) => Promise<NextResponse | Response>;

type AuthedValidatedHandler<T> = (
  req: AuthedParsedRequest<T>,
  context: RouteContext
) => Promise<NextResponse | Response>;

function makeValidationHandler<T>(
  schema: ZodSchema<T>,
  handler: ValidatedHandler<T> | AuthedValidatedHandler<T>
): RouteHandler {
  return async (req: Request, context: RouteContext): Promise<NextResponse | Response> => {
    let rawBody: unknown;
    try {
      rawBody = await req.json();
    } catch {
      const err = new ValidationError('Request body must be valid JSON');
      return NextResponse.json(err.toJSON(), { status: 400 });
    }

    const parsed = schema.safeParse(rawBody);
    if (!parsed.success) {
      const fields: Record<string, string[]> = {};
      for (const issue of parsed.error.issues) {
        const key = issue.path.join('.') || 'root';
        if (!fields[key]) fields[key] = [];
        fields[key].push(issue.message);
      }
      const err = new ValidationError('Validation failed', fields);
      return NextResponse.json(err.toJSON(), { status: 400 });
    }

    Object.defineProperty(req, 'parsedBody', {
      value: parsed.data,
      writable: false,
      enumerable: true,
      configurable: true,
    });

    return (handler as ValidatedHandler<T>)(req as ParsedRequest<T>, context);
  };
}

export function withValidation<T>(
  schema: ZodSchema<T>,
  handler: ValidatedHandler<T>
): RouteHandler {
  return makeValidationHandler(schema, handler);
}

export function withValidationAuthed<T>(
  schema: ZodSchema<T>,
  handler: AuthedValidatedHandler<T>
): RouteHandler {
  return makeValidationHandler(schema, handler);
}
