/**
 * Stripe Correlation ID Integration
 *
 * Provides utilities for including correlation IDs in Stripe API calls.
 * This enables tracing requests from our system through to Stripe's webhook responses.
 *
 * Correlation IDs are stored in Stripe metadata which:
 * - Is included in webhook payloads
 * - Appears in the Stripe Dashboard
 * - Can be searched in Stripe logs
 *
 * Usage:
 * ```ts
 * import { addCorrelationToMetadata, getCorrelationFromStripeObject } from '@/lib/stripe/correlation';
 *
 * // When creating Stripe objects
 * const customer = await stripe.customers.create({
 *   email: 'user@example.com',
 *   metadata: addCorrelationToMetadata({ customField: 'value' }),
 * });
 *
 * // In webhook handlers
 * const correlationId = getCorrelationFromStripeObject(event.data.object);
 * logger.withContext(undefined, correlationId).info('Processing webhook');
 * ```
 */

import Stripe from 'stripe';
import { getCurrentCorrelationId, getCurrentRequestId } from '../correlation-id';
import { logger } from '../logger';

// =============================================================================
// Constants
// =============================================================================

/** Metadata key for correlation ID in Stripe objects */
export const STRIPE_CORRELATION_ID_KEY = 'mawidi_correlation_id';

/** Metadata key for request ID in Stripe objects */
export const STRIPE_REQUEST_ID_KEY = 'mawidi_request_id';

/** Metadata key for environment (useful for debugging) */
export const STRIPE_ENVIRONMENT_KEY = 'mawidi_environment';

// =============================================================================
// Metadata Helpers
// =============================================================================

/**
 * Add correlation IDs to Stripe metadata.
 * Merges with existing metadata, adding correlation context.
 *
 * @param existingMetadata - Existing metadata to merge with
 * @returns Metadata object with correlation IDs added
 *
 * @example
 * const customer = await stripe.customers.create({
 *   email: 'user@example.com',
 *   metadata: addCorrelationToMetadata({ user_id: 'usr_123' }),
 * });
 */
export function addCorrelationToMetadata(
  existingMetadata?: Stripe.MetadataParam | null
): Stripe.MetadataParam {
  const correlationId = getCurrentCorrelationId();
  const requestId = getCurrentRequestId();
  const environment = process.env.NODE_ENV || 'development';

  const correlationMetadata: Stripe.MetadataParam = {};

  if (correlationId) {
    correlationMetadata[STRIPE_CORRELATION_ID_KEY] = correlationId;
  }
  if (requestId) {
    correlationMetadata[STRIPE_REQUEST_ID_KEY] = requestId;
  }
  correlationMetadata[STRIPE_ENVIRONMENT_KEY] = environment;

  return {
    ...existingMetadata,
    ...correlationMetadata,
  };
}

/**
 * Extract correlation ID from a Stripe object's metadata.
 * Useful in webhook handlers to link Stripe events back to original requests.
 *
 * @param stripeObject - Stripe object with metadata
 * @returns Correlation ID if present, undefined otherwise
 *
 * @example
 * // In webhook handler
 * const invoice = event.data.object as Stripe.Invoice;
 * const correlationId = getCorrelationFromStripeObject(invoice);
 * if (correlationId) {
 *   logger.setCorrelationId(correlationId);
 * }
 */
export function getCorrelationFromStripeObject(
  stripeObject: { metadata?: Stripe.Metadata | null } | null | undefined
): string | undefined {
  if (!stripeObject?.metadata) return undefined;
  return stripeObject.metadata[STRIPE_CORRELATION_ID_KEY];
}

/**
 * Extract request ID from a Stripe object's metadata.
 *
 * @param stripeObject - Stripe object with metadata
 * @returns Request ID if present, undefined otherwise
 */
export function getRequestIdFromStripeObject(
  stripeObject: { metadata?: Stripe.Metadata | null } | null | undefined
): string | undefined {
  if (!stripeObject?.metadata) return undefined;
  return stripeObject.metadata[STRIPE_REQUEST_ID_KEY];
}

/**
 * Extract environment from a Stripe object's metadata.
 *
 * @param stripeObject - Stripe object with metadata
 * @returns Environment string if present, undefined otherwise
 */
export function getEnvironmentFromStripeObject(
  stripeObject: { metadata?: Stripe.Metadata | null } | null | undefined
): string | undefined {
  if (!stripeObject?.metadata) return undefined;
  return stripeObject.metadata[STRIPE_ENVIRONMENT_KEY];
}

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

/**
 * Log a Stripe API call with correlation context.
 * Call this before making Stripe API calls for tracing.
 *
 * @param operation - Stripe operation name (e.g., 'customers.create')
 * @param params - Parameters being passed (sanitized)
 */
export function logStripeApiCall(
  operation: string,
  params?: Record<string, unknown>
): void {
  logger.debug(`Stripe API call: ${operation}`, {
    stripeOperation: operation,
    // Don't log sensitive params, just the operation type
    hasParams: !!params,
  });
}

/**
 * Log a Stripe API response with correlation context.
 *
 * @param operation - Stripe operation name
 * @param objectId - ID of the created/updated object
 * @param objectType - Type of Stripe object
 */
export function logStripeApiResponse(
  operation: string,
  objectId?: string,
  objectType?: string
): void {
  logger.debug(`Stripe API response: ${operation}`, {
    stripeOperation: operation,
    stripeObjectId: objectId,
    stripeObjectType: objectType,
  });
}

/**
 * Stripe error type (simplified for type safety)
 */
interface StripeErrorLike {
  type?: string;
  code?: string;
  message: string;
}

/**
 * Log a Stripe API error with correlation context.
 *
 * @param operation - Stripe operation name
 * @param error - The error that occurred
 */
export function logStripeApiError(
  operation: string,
  error: Error | StripeErrorLike
): void {
  const isStripeError = 'type' in error && 'code' in error;

  logger.error(`Stripe API error: ${operation}`, error instanceof Error ? error : new Error(error.message), {
    stripeOperation: operation,
    stripeErrorType: isStripeError ? (error as StripeErrorLike).type : undefined,
    stripeErrorCode: isStripeError ? (error as StripeErrorLike).code : undefined,
  });
}

// =============================================================================
// Webhook Helpers
// =============================================================================

/**
 * Initialize correlation context from a Stripe webhook event.
 * Extracts correlation ID from the event's data object metadata.
 *
 * @param event - Stripe webhook event
 * @returns Object with extracted IDs for logging setup
 *
 * @example
 * export async function POST(request: NextRequest) {
 *   const event = await constructEvent(request);
 *   const { correlationId, requestId } = initializeCorrelationFromWebhook(event);
 *
 *   // Set up logging context
 *   if (correlationId) {
 *     logger.setCorrelationId(correlationId);
 *   }
 *
 *   // Process event...
 * }
 */
export function initializeCorrelationFromWebhook(
  event: Stripe.Event
): { correlationId?: string; requestId?: string; environment?: string } {
  const dataObject = event.data.object as { metadata?: Stripe.Metadata | null };

  const correlationId = getCorrelationFromStripeObject(dataObject);
  const requestId = getRequestIdFromStripeObject(dataObject);
  const environment = getEnvironmentFromStripeObject(dataObject);

  // Log webhook receipt with extracted correlation
  logger.info(`Stripe webhook received: ${event.type}`, {
    stripeEventId: event.id,
    stripeEventType: event.type,
    originalCorrelationId: correlationId,
    originalRequestId: requestId,
    originalEnvironment: environment,
  });

  return { correlationId, requestId, environment };
}

// =============================================================================
// Idempotency Key Helpers
// =============================================================================

/**
 * Generate an idempotency key that includes correlation ID.
 * This ensures retries of the same logical operation are properly handled by Stripe.
 *
 * @param operation - Operation name for namespacing
 * @param uniqueId - Unique identifier for this specific operation
 * @returns Idempotency key string
 *
 * @example
 * const idempotencyKey = generateIdempotencyKey('create_subscription', userId);
 * const subscription = await stripe.subscriptions.create(
 *   { ... },
 *   { idempotencyKey }
 * );
 */
export function generateIdempotencyKey(
  operation: string,
  uniqueId: string
): string {
  const correlationId = getCurrentCorrelationId();
  const timestamp = Date.now();

  // Include correlation ID to link retries to the same request flow
  // Include timestamp to prevent conflicts on legitimate retries of different requests
  if (correlationId) {
    return `${operation}_${uniqueId}_${correlationId}`;
  }

  return `${operation}_${uniqueId}_${timestamp}`;
}
