/**
 * API Request Context Module
 *
 * Provides utilities for API routes to work with correlation IDs and request context.
 * This module bridges the middleware-set correlation headers with the application code.
 *
 * Usage in API routes:
 * ```ts
 * import { withRequestContext, getRequestCorrelationId } from '@/lib/api-request-context';
 *
 * export async function POST(request: NextRequest) {
 *   return withRequestContext(request, async () => {
 *     // All async code here has access to correlation context
 *     logger.info('Processing request'); // Automatically includes correlationId
 *     await stripeWithCorrelation.customers.create({...});
 *   });
 * }
 * ```
 */

import { NextRequest, NextResponse } from "next/server";
import { headers } from "next/headers";
import {
  CORRELATION_ID_HEADER,
  REQUEST_ID_HEADER,
  runWithCorrelation,
  createCorrelationContext,
  generateCorrelationId,
  generateRequestId,
  getCurrentCorrelationId,
  getCurrentRequestId,
  getCorrelationContext,
  type CorrelationContext,
} from "./correlation-id";
import { logger } from "./logger";

// =============================================================================
// Types
// =============================================================================

export interface RequestContextOptions {
  /** User ID to include in correlation context */
  userId?: string;
  /** Organization ID to include in correlation context */
  organizationId?: string;
  /** Session ID to include in correlation context */
  sessionId?: string;
}

// =============================================================================
// Header Extraction
// =============================================================================

/**
 * Extract correlation ID from Next.js request headers
 * Falls back to generating a new one if not present
 */
export function getCorrelationIdFromRequest(request: NextRequest): string {
  return request.headers.get(CORRELATION_ID_HEADER) || generateCorrelationId();
}

/**
 * Extract request ID from Next.js request headers
 * Falls back to generating a new one if not present
 */
export function getRequestIdFromRequest(request: NextRequest): string {
  return request.headers.get(REQUEST_ID_HEADER) || generateRequestId();
}

/**
 * Get correlation ID from Next.js headers() in server components
 * This works in API routes and Server Components
 */
export async function getCorrelationIdFromHeaders(): Promise<string> {
  const headersList = await headers();
  return headersList.get(CORRELATION_ID_HEADER) || generateCorrelationId();
}

/**
 * Get request ID from Next.js headers() in server components
 */
export async function getRequestIdFromHeaders(): Promise<string> {
  const headersList = await headers();
  return headersList.get(REQUEST_ID_HEADER) || generateRequestId();
}

// =============================================================================
// Request Context Wrapper
// =============================================================================

/**
 * Run an async function with correlation context set from the request.
 * This enables automatic correlation ID propagation to all downstream calls.
 *
 * @param request - The Next.js request object
 * @param handler - Async function to run with context
 * @param options - Additional context options (userId, organizationId)
 * @returns The result of the handler
 *
 * @example
 * export async function POST(request: NextRequest) {
 *   return withRequestContext(request, async () => {
 *     // Correlation ID is automatically available here
 *     logger.info('Processing request');
 *     const result = await processData();
 *     return NextResponse.json(result);
 *   });
 * }
 */
export function withRequestContext<T>(
  request: NextRequest,
  handler: () => Promise<T>,
  options: RequestContextOptions = {},
): Promise<T> {
  const correlationId = getCorrelationIdFromRequest(request);
  const requestId = getRequestIdFromRequest(request);

  const context = createCorrelationContext({
    correlationId,
    requestId,
    userId: options.userId,
    organizationId: options.organizationId,
    sessionId: options.sessionId,
  });

  return runWithCorrelation(context, handler);
}

/**
 * Run an async function with correlation context, for use in server components
 * or when you don't have direct access to the request object.
 *
 * @param handler - Async function to run with context
 * @param options - Additional context options
 */
export async function withServerContext<T>(
  handler: () => Promise<T>,
  options: RequestContextOptions = {},
): Promise<T> {
  const correlationId = await getCorrelationIdFromHeaders();
  const requestId = await getRequestIdFromHeaders();

  const context = createCorrelationContext({
    correlationId,
    requestId,
    userId: options.userId,
    organizationId: options.organizationId,
    sessionId: options.sessionId,
  });

  return runWithCorrelation(context, handler);
}

// =============================================================================
// Response Helpers
// =============================================================================

/**
 * Create a JSON response with correlation headers included
 *
 * @param data - Response data
 * @param init - Response init options
 */
export function jsonResponseWithCorrelation<T>(
  data: T,
  init?: ResponseInit,
): NextResponse<T> {
  const correlationId = getCurrentCorrelationId();
  const requestId = getCurrentRequestId();

  const headers = new Headers(init?.headers);
  if (correlationId) {
    headers.set(CORRELATION_ID_HEADER, correlationId);
  }
  if (requestId) {
    headers.set(REQUEST_ID_HEADER, requestId);
  }

  return NextResponse.json(data, {
    ...init,
    headers,
  });
}

/**
 * Create an error response with correlation headers included
 * Useful for debugging - clients can reference the correlationId in support requests
 *
 * @param error - Error message
 * @param status - HTTP status code
 * @param details - Additional error details
 */
export function errorResponseWithCorrelation(
  error: string,
  status: number = 500,
  details?: Record<string, unknown>,
): NextResponse {
  const correlationId = getCurrentCorrelationId();
  const requestId = getCurrentRequestId();

  const responseBody = {
    error,
    correlationId,
    ...(details || {}),
  };

  const headers = new Headers();
  if (correlationId) {
    headers.set(CORRELATION_ID_HEADER, correlationId);
  }
  if (requestId) {
    headers.set(REQUEST_ID_HEADER, requestId);
  }

  // Log the error with correlation context
  logger.error(`API Error: ${error}`, undefined, {
    status,
    ...details,
  });

  return NextResponse.json(responseBody, { status, headers });
}

// =============================================================================
// Context Accessors
// =============================================================================

/**
 * Get the current correlation ID from context
 * Returns undefined if not in a request context
 */
export function getRequestCorrelationId(): string | undefined {
  return getCurrentCorrelationId();
}

/**
 * Get the current request ID from context
 * Returns undefined if not in a request context
 */
export function getRequestRequestId(): string | undefined {
  return getCurrentRequestId();
}

/**
 * Get the full correlation context
 * Returns undefined if not in a request context
 */
export function getFullRequestContext(): CorrelationContext | undefined {
  return getCorrelationContext();
}

// =============================================================================
// Logging Helpers
// =============================================================================

/**
 * Log an API request start with correlation context
 *
 * @param method - HTTP method
 * @param path - Request path
 * @param meta - Additional metadata
 */
export function logRequestStart(
  method: string,
  path: string,
  meta?: Record<string, unknown>,
): void {
  logger.info(`${method} ${path} - Request started`, {
    method,
    path,
    ...meta,
  });
}

/**
 * Log an API request completion with timing
 *
 * @param method - HTTP method
 * @param path - Request path
 * @param status - Response status code
 * @param durationMs - Request duration in milliseconds
 * @param meta - Additional metadata
 */
export function logRequestComplete(
  method: string,
  path: string,
  status: number,
  durationMs?: number,
  meta?: Record<string, unknown>,
): void {
  const context = getCorrelationContext();
  const duration =
    durationMs ?? (context ? Date.now() - context.startTime : undefined);

  logger.info(`${method} ${path} - Request completed`, {
    method,
    path,
    status,
    durationMs: duration,
    ...meta,
  });
}

/**
 * Log an API request error
 *
 * @param method - HTTP method
 * @param path - Request path
 * @param error - Error object or message
 * @param meta - Additional metadata
 */
export function logRequestError(
  method: string,
  path: string,
  error: Error | string,
  meta?: Record<string, unknown>,
): void {
  const errorObj = error instanceof Error ? error : new Error(error);

  logger.error(`${method} ${path} - Request failed`, errorObj, {
    method,
    path,
    ...meta,
  });
}
