/**
 * @fileoverview Utility functions for Aurora API route handlers.
 * Provides helpers for response formatting, parameter parsing, and validation.
 */

import { z, ZodError, type ZodSchema } from "zod";
import type { ApiErrorResponse } from "./types";

/**
 * JSON replacer function that handles BigInt serialization.
 * Converts BigInt values to strings for JSON compatibility.
 */
export function jsonReplacer(_key: string, value: unknown): unknown {
    return typeof value === "bigint" ? value.toString() : value;
}

/**
 * Creates a JSON response with proper content-type header and BigInt handling.
 * 
 * @param data - The data to serialize as JSON
 * @param status - HTTP status code (default: 200)
 * @returns A Response object with JSON content
 * 
 * @example
 * return jsonResponse({ items: [...], total: 10 });
 * return jsonResponse({ success: true, item }, 201);
 */
export function jsonResponse<T>(data: T, status: number = 200): Response {
    return new Response(JSON.stringify(data, jsonReplacer), {
        status,
        headers: { "Content-Type": "application/json" }
    });
}

/**
 * Creates a standardized error response.
 * 
 * @param error - Error message
 * @param status - HTTP status code (default: 500)
 * @param details - Optional additional error details
 * @returns A Response object with error JSON
 * 
 * @example
 * return errorResponse("Item not found", 404);
 * return errorResponse("Validation failed", 400, "Name is required");
 */
export function errorResponse(
    error: string,
    status: number = 500,
    details?: string
): Response {
    const body: ApiErrorResponse = { error };
    if (details) body.details = details;

    return Response.json(body, { status });
}

/**
 * Creates a validation error response from a ZodError.
 * 
 * @param zodError - The ZodError from a failed parse
 * @returns A 400 Response with validation issue details
 */
export function validationErrorResponse(zodError: ZodError): Response {
    return Response.json(
        {
            error: "Invalid payload",
            issues: zodError.issues.map(issue => ({
                path: issue.path,
                message: issue.message
            }))
        },
        { status: 400 }
    );
}

/**
 * Parses and validates a request body against a Zod schema.
 * 
 * @param req - The HTTP request
 * @param schema - Zod schema to validate against
 * @returns Validated data or an error Response
 * 
 * @example
 * const result = await parseBody(req, CreateItemSchema);
 * if (result instanceof Response) return result; // Validation failed
 * const data = result; // Type-safe validated data
 */
export async function parseBody<T extends ZodSchema>(
    req: Request,
    schema: T
): Promise<z.infer<T> | Response> {
    try {
        const rawBody = await req.json();
        const parsed = schema.safeParse(rawBody);

        if (!parsed.success) {
            return validationErrorResponse(parsed.error);
        }

        return parsed.data;
    } catch (e) {
        return errorResponse("Invalid JSON body", 400);
    }
}

/**
 * Parses query parameters against a Zod schema.
 * 
 * @param url - The URL containing query parameters
 * @param schema - Zod schema to validate against
 * @returns Validated query params or an error Response
 */
export function parseQuery<T extends ZodSchema>(
    url: URL,
    schema: T
): z.infer<T> | Response {
    const params: Record<string, string> = {};
    url.searchParams.forEach((value, key) => {
        params[key] = value;
    });

    const parsed = schema.safeParse(params);

    if (!parsed.success) {
        return validationErrorResponse(parsed.error);
    }

    return parsed.data;
}

/**
 * Extracts a numeric ID from a URL path segment.
 * 
 * @param pathname - The URL pathname
 * @param position - Position from the end (0 = last segment, 1 = second-to-last, etc.)
 * @returns The parsed integer ID or null if invalid
 * 
 * @example
 * parseIdFromPath("/api/items/123") // returns 123
 * parseIdFromPath("/api/items/abc") // returns null
 * parseIdFromPath("/api/users/456/inventory", 1) // returns 456
 */
export function parseIdFromPath(pathname: string, position: number = 0): number | null {
    const segments = pathname.split("/").filter(Boolean);
    const segment = segments[segments.length - 1 - position];

    if (!segment) return null;

    const id = parseInt(segment, 10);
    return isNaN(id) ? null : id;
}

/**
 * Extracts a string ID (like Discord snowflake) from a URL path segment.
 * 
 * @param pathname - The URL pathname
 * @param position - Position from the end (0 = last segment)
 * @returns The string ID or null if segment doesn't exist
 */
export function parseStringIdFromPath(pathname: string, position: number = 0): string | null {
    const segments = pathname.split("/").filter(Boolean);
    const segment = segments[segments.length - 1 - position];
    return segment || null;
}

/**
 * Checks if a pathname matches a pattern with optional parameter placeholders.
 * 
 * @param pathname - The actual URL pathname
 * @param pattern - The pattern to match (use :id for numeric params, :param for string params)
 * @returns True if the pattern matches
 * 
 * @example
 * matchPath("/api/items/123", "/api/items/:id") // true
 * matchPath("/api/items", "/api/items/:id") // false
 */
export function matchPath(pathname: string, pattern: string): boolean {
    const pathParts = pathname.split("/").filter(Boolean);
    const patternParts = pattern.split("/").filter(Boolean);

    if (pathParts.length !== patternParts.length) return false;

    return patternParts.every((part, i) => {
        if (part.startsWith(":")) return true; // Matches any value
        return part === pathParts[i];
    });
}

/**
 * Wraps an async route handler with consistent error handling.
 * Catches all errors and returns appropriate error responses.
 * 
 * @param handler - The async handler function
 * @param logContext - Context string for error logging
 * @returns A wrapped handler with error handling
 */
export function withErrorHandling(
    handler: () => Promise<Response>,
    logContext: string
): Promise<Response> {
    return handler().catch((error: unknown) => {
        // Dynamic import to avoid circular dependencies
        return import("@shared/lib/logger").then(({ logger }) => {
            logger.error("web", `Error in ${logContext}`, error);
            return errorResponse(
                `Failed to ${logContext.toLowerCase()}`,
                500,
                error instanceof Error ? error.message : String(error)
            );
        });
    });
}
