/**
 * External Service Client with Correlation ID Support
 *
 * Provides a wrapper around fetch() and other external service calls
 * that automatically includes correlation headers for distributed tracing.
 *
 * Usage:
 * ```ts
 * import { externalFetch, createServiceClient } from '@/lib/external-service-client';
 *
 * // Simple fetch with correlation headers
 * const response = await externalFetch('https://api.example.com/data');
 *
 * // Create a reusable client for a specific service
 * const apiClient = createServiceClient('https://api.example.com', {
 *   serviceName: 'example-api',
 *   defaultHeaders: { 'Authorization': `Bearer ${token}` },
 * });
 * const data = await apiClient.get('/users');
 * ```
 */

import {
  CORRELATION_ID_HEADER,
  REQUEST_ID_HEADER,
  getCurrentCorrelationId,
  getCurrentRequestId,
  getCorrelationHeaders,
} from "./correlation-id";
import { logger } from "./logger";

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

export interface ServiceClientOptions {
  /** Human-readable name for the service (used in logs) */
  serviceName: string;
  /** Default headers to include in all requests */
  defaultHeaders?: Record<string, string>;
  /** Timeout in milliseconds (default: 30000) */
  timeout?: number;
  /** Whether to log request/response details (default: true) */
  enableLogging?: boolean;
  /** Custom fetch implementation (useful for testing) */
  customFetch?: typeof fetch;
}

export interface RequestOptions extends Omit<RequestInit, "body"> {
  /** Request body (will be JSON stringified if object) */
  body?: unknown;
  /** Query parameters to append to URL */
  params?: Record<string, string | number | boolean | undefined>;
  /** Additional headers for this specific request */
  headers?: Record<string, string>;
  /** Custom timeout for this request (overrides client default) */
  timeout?: number;
  /** Skip correlation headers (not recommended) */
  skipCorrelation?: boolean;
}

export interface ServiceResponse<T = unknown> {
  data: T;
  status: number;
  headers: Headers;
  correlationId?: string;
  requestId?: string;
  durationMs: number;
}

export interface ServiceError extends Error {
  status?: number;
  response?: unknown;
  correlationId?: string;
  serviceName?: string;
}

// =============================================================================
// External Fetch with Correlation
// =============================================================================

/**
 * Fetch wrapper that automatically includes correlation headers.
 * Drop-in replacement for fetch() with correlation support.
 *
 * @param input - URL or Request object
 * @param init - Fetch init options
 * @returns Fetch Response
 *
 * @example
 * const response = await externalFetch('https://api.example.com/data', {
 *   method: 'POST',
 *   headers: { 'Content-Type': 'application/json' },
 *   body: JSON.stringify({ key: 'value' }),
 * });
 */
export async function externalFetch(
  input: RequestInfo | URL,
  init?: RequestInit,
): Promise<Response> {
  const correlationHeaders = getCorrelationHeaders();
  const headers = new Headers(init?.headers);

  // Add correlation headers
  Object.entries(correlationHeaders).forEach(([key, value]) => {
    if (value) headers.set(key, value);
  });

  return fetch(input, {
    ...init,
    headers,
  });
}

// =============================================================================
// Service Client Factory
// =============================================================================

/**
 * Create a reusable client for a specific external service.
 * Includes automatic correlation headers, logging, and error handling.
 *
 * @param baseUrl - Base URL for the service
 * @param options - Client configuration options
 * @returns Service client with get, post, put, patch, delete methods
 *
 * @example
 * const stripeApi = createServiceClient('https://api.stripe.com', {
 *   serviceName: 'stripe',
 *   defaultHeaders: { 'Authorization': `Bearer ${apiKey}` },
 * });
 *
 * const customer = await stripeApi.get('/v1/customers/cus_123');
 * const newCustomer = await stripeApi.post('/v1/customers', { email: 'user@example.com' });
 */
export function createServiceClient(
  baseUrl: string,
  options: ServiceClientOptions,
) {
  const {
    serviceName,
    defaultHeaders = {},
    timeout = 30000,
    enableLogging = true,
    customFetch = fetch,
  } = options;

  /**
   * Make a request to the service
   */
  async function request<T = unknown>(
    method: string,
    path: string,
    requestOptions: RequestOptions = {},
  ): Promise<ServiceResponse<T>> {
    const startTime = Date.now();
    const correlationId = getCurrentCorrelationId();
    const requestId = getCurrentRequestId();

    // Build URL with query params
    let url = `${baseUrl}${path}`;
    if (requestOptions.params) {
      const searchParams = new URLSearchParams();
      Object.entries(requestOptions.params).forEach(([key, value]) => {
        if (value !== undefined) {
          searchParams.set(key, String(value));
        }
      });
      const queryString = searchParams.toString();
      if (queryString) {
        url += (url.includes("?") ? "&" : "?") + queryString;
      }
    }

    // Build headers
    const headers = new Headers();
    Object.entries(defaultHeaders).forEach(([key, value]) => {
      headers.set(key, value);
    });
    if (requestOptions.headers) {
      Object.entries(requestOptions.headers).forEach(([key, value]) => {
        headers.set(key, value);
      });
    }

    // Add correlation headers unless explicitly skipped
    if (!requestOptions.skipCorrelation) {
      if (correlationId) headers.set(CORRELATION_ID_HEADER, correlationId);
      if (requestId) headers.set(REQUEST_ID_HEADER, requestId);
    }

    // Prepare body
    let body: string | undefined;
    if (requestOptions.body !== undefined) {
      if (typeof requestOptions.body === "string") {
        body = requestOptions.body;
      } else {
        body = JSON.stringify(requestOptions.body);
        if (!headers.has("Content-Type")) {
          headers.set("Content-Type", "application/json");
        }
      }
    }

    // Log request start
    if (enableLogging) {
      logger.debug(`External API call: ${serviceName} ${method} ${path}`, {
        serviceName,
        method,
        path,
        hasBody: !!body,
      });
    }

    // Make request with timeout
    const controller = new AbortController();
    const timeoutId = setTimeout(
      () => controller.abort(),
      requestOptions.timeout || timeout,
    );

    try {
      const response = await customFetch(url, {
        method,
        headers,
        body,
        signal: controller.signal,
      });

      clearTimeout(timeoutId);
      const durationMs = Date.now() - startTime;

      // Parse response
      let data: T;
      const contentType = response.headers.get("content-type");
      if (contentType?.includes("application/json")) {
        data = await response.json();
      } else {
        data = (await response.text()) as unknown as T;
      }

      // Log response
      if (enableLogging) {
        logger.debug(
          `External API response: ${serviceName} ${method} ${path}`,
          {
            serviceName,
            method,
            path,
            status: response.status,
            durationMs,
          },
        );
      }

      // Handle error responses
      if (!response.ok) {
        const error: ServiceError = new Error(
          `${serviceName} API error: ${response.status} ${response.statusText}`,
        );
        error.status = response.status;
        error.response = data;
        error.correlationId = correlationId;
        error.serviceName = serviceName;

        if (enableLogging) {
          logger.error(
            `External API error: ${serviceName} ${method} ${path}`,
            error,
            {
              serviceName,
              method,
              path,
              status: response.status,
              durationMs,
            },
          );
        }

        throw error;
      }

      return {
        data,
        status: response.status,
        headers: response.headers,
        correlationId,
        requestId,
        durationMs,
      };
    } catch (error) {
      clearTimeout(timeoutId);
      const durationMs = Date.now() - startTime;

      // Handle abort (timeout)
      if (error instanceof Error && error.name === "AbortError") {
        const timeoutError: ServiceError = new Error(
          `${serviceName} API timeout after ${requestOptions.timeout || timeout}ms`,
        );
        timeoutError.correlationId = correlationId;
        timeoutError.serviceName = serviceName;

        if (enableLogging) {
          logger.error(
            `External API timeout: ${serviceName} ${method} ${path}`,
            timeoutError,
            {
              serviceName,
              method,
              path,
              durationMs,
              timeout: requestOptions.timeout || timeout,
            },
          );
        }

        throw timeoutError;
      }

      // Re-throw other errors
      throw error;
    }
  }

  return {
    /**
     * Make a GET request
     */
    get: <T = unknown>(path: string, options?: RequestOptions) =>
      request<T>("GET", path, options),

    /**
     * Make a POST request
     */
    post: <T = unknown>(
      path: string,
      body?: unknown,
      options?: RequestOptions,
    ) => request<T>("POST", path, { ...options, body }),

    /**
     * Make a PUT request
     */
    put: <T = unknown>(
      path: string,
      body?: unknown,
      options?: RequestOptions,
    ) => request<T>("PUT", path, { ...options, body }),

    /**
     * Make a PATCH request
     */
    patch: <T = unknown>(
      path: string,
      body?: unknown,
      options?: RequestOptions,
    ) => request<T>("PATCH", path, { ...options, body }),

    /**
     * Make a DELETE request
     */
    delete: <T = unknown>(path: string, options?: RequestOptions) =>
      request<T>("DELETE", path, options),

    /**
     * Make a custom request
     */
    request,
  };
}

