/**
 * Central API response helpers for consistent JSON output and standardized errors.
 *
 * Why this exists:
 * - Route handlers should NOT each invent their own error JSON shape.
 * - The frontend (and tests/logging) should rely on a stable contract:
 *   { error: { message, code, details? } }
 * - We want consistent HTTP status codes (400/401/403/404/500).
 *
 * This file intentionally uses the Web Fetch `Response` object (not NextResponse),
 * because Next.js route handlers can return any `Response` and it keeps tests simple.
 */

/**
 * Default JSON response headers for all API responses that return JSON.
 *
 * RHL-006 (Caching / Freshness):
 * - We explicitly disable HTTP caching for ALL JSON API responses by default.
 * - Reason #1: NAS filesystem contents can change at any time (new scans appear).
 * - Reason #2: Auth/RBAC-protected responses must never be cached/shared by accident
 *   (e.g. via browser cache, proxies, reverse proxies, or intermediate gateways).
 *
 * If a future endpoint truly needs caching, it should override headers explicitly
 * at the call site via `json(..., status, { "Cache-Control": "..." })`.
 */
const JSON_HEADERS = {
	"Content-Type": "application/json",
	"Cache-Control": "no-store",
};

/**
 * Create a JSON `Response` with the given payload.
 *
 * @param {any} data - The JSON-serializable payload.
 * @param {number} status - HTTP status code.
 * @param {Record<string,string>} headers - Optional extra headers.
 * @returns {Response}
 */
export function json(data, status = 200, headers = {}) {
	return new Response(JSON.stringify(data), {
		status,
		headers: { ...JSON_HEADERS, ...headers },
	});
}

/**
 * Build the standardized error payload.
 *
 * Contract:
 * {
 *   "error": {
 *     "message": "Human readable message",
 *     "code": "SOME_MACHINE_CODE",
 *     "details": { ... } // optional
 *   }
 * }
 *
 * IMPORTANT:
 * - message is for humans (UI/logs).
 * - code is for machines (frontend decisions, monitoring, tests).
 * - details is optional and should only include safe data.
 *
 * @param {{code:string, message:string, details?:any}} input
 * @returns {{error:{message:string, code:string, details?:any}}}
 */
export function buildErrorPayload({ code, message, details }) {
	const error = { message, code };

	// Only include details when explicitly provided.
	// This keeps the error payload minimal and predictable.
	if (details !== undefined) error.details = details;

	return { error };
}

/**
 * Create a standardized JSON error response.
 *
 * @param {number} status - HTTP status code (400/401/403/404/500).
 * @param {string} code - Stable machine-readable error code.
 * @param {string} message - Human-readable message (safe to expose).
 * @param {any=} details - Optional structured details (validation fields, params, etc.).
 * @returns {Response}
 */
export function jsonError(status, code, message, details) {
	return json(buildErrorPayload({ code, message, details }), status);
}

/**
 * Custom error type for “expected” API failures.
 *
 * We throw ApiError from route handlers instead of returning ad-hoc JSON.
 * The `withErrorHandling` wrapper converts it into a standardized response.
 */
export class ApiError extends Error {
	/**
	 * @param {{
	 *   status: number,
	 *   code: string,
	 *   message: string,
	 *   details?: any,
	 *   cause?: any
	 * }} input
	 */
	constructor({ status, code, message, details, cause }) {
		// Modern Node supports `cause`, which preserves the original error chain.
		super(message, cause ? { cause } : undefined);

		this.name = "ApiError";
		this.status = status;
		this.code = code;

		// Only attach details if explicitly provided.
		if (details !== undefined) this.details = details;
	}
}

/**
 * Convenience constructors for the common HTTP statuses used by the API.
 * This prevents magic numbers and keeps code readable in route handlers.
 */

export function badRequest(code, message, details) {
	return new ApiError({ status: 400, code, message, details });
}

export function unauthorized(code, message, details) {
	return new ApiError({ status: 401, code, message, details });
}

export function forbidden(code, message, details) {
	return new ApiError({ status: 403, code, message, details });
}

export function notFound(code, message, details) {
	return new ApiError({ status: 404, code, message, details });
}

/**
 * Convert a thrown error into a standardized API response.
 *
 * Policy:
 * - If it's an ApiError, we trust its status/code/message (they should be safe).
 * - Otherwise, we return a generic 500 without leaking internals.
 *
 * @param {unknown} err
 * @returns {Response}
 */
export function toApiErrorResponse(err) {
	if (err instanceof ApiError) {
		return jsonError(err.status, err.code, err.message, err.details);
	}

	// Never leak unexpected error details to clients.
	return jsonError(500, "INTERNAL_SERVER_ERROR", "Internal server error");
}

/**
 * Wrap a Next.js route handler to enforce consistent error handling.
 *
 * Usage:
 * export const GET = withErrorHandling(async function GET(request, ctx) { ... })
 *
 * Benefits:
 * - No repeated try/catch blocks in every route.
 * - All thrown ApiErrors become standardized JSON responses.
 * - Unknown errors become a safe, generic 500 response.
 *
 * @param {Function} handler
 * @param {{logPrefix?:string}=} options
 * @returns {Function}
 */
export function withErrorHandling(handler, { logPrefix = "[api]" } = {}) {
	const shouldLog = process.env.NODE_ENV !== "test";

	return async (...args) => {
		try {
			return await handler(...args);
		} catch (err) {
			if (err instanceof ApiError) {
				if (shouldLog && err.status >= 500) {
					console.error(`${logPrefix} ${err.code}:`, err.cause || err);
				}
				return toApiErrorResponse(err);
			}

			if (shouldLog) {
				console.error(`${logPrefix} Unhandled error:`, err);
			}
			return toApiErrorResponse(err);
		}
	};
}
