/**
 * HTTP Error Mapper
 *
 * Shared utility for mapping HTTP status codes to ToolingError types.
 * Used by all integration adapters for consistent error handling.
 *
 * Standard mapping:
 * - 401/403 -> credentials_invalid (authentication failed)
 * - 400/404/409 -> validation_error (non-retryable client errors)
 * - 429 -> rate_limit_exceeded (retryable)
 * - 500+ -> server_unavailable (retryable)
 * - Other 4xx -> validation_error (non-retryable)
 * - Other -> api_error (retryable)
 */

import { ToolingError, type ToolingErrorType } from '../../execution/engine/tools/types'

/**
 * Context for HTTP error creation
 */
export interface HttpErrorContext {
  integration: string
  method: string
  statusCode?: number
  organizationId?: string
  [key: string]: unknown
}

/**
 * Custom error parser function type
 * Allows adapters to extract error messages from API-specific response formats
 */
export type ErrorParser = (responseBody: string) => string | undefined

/**
 * Map HTTP status code to ToolingError type
 *
 * Standard mapping used by all integration adapters:
 * - 401/403 -> credentials_invalid (authentication failed)
 * - 400/404/409 -> validation_error (non-retryable client errors)
 * - 429 -> rate_limit_exceeded (retryable)
 * - 500+ -> server_unavailable (retryable)
 * - Other 4xx -> validation_error (non-retryable)
 * - Other -> api_error (retryable)
 *
 * @param status - HTTP status code
 * @returns ToolingErrorType for the status code
 */
export function mapHttpStatusToErrorType(status: number): ToolingErrorType {
  if (status === 401 || status === 403) return 'credentials_invalid'
  if (status === 404 || status === 400 || status === 409) return 'validation_error'
  if (status === 429) return 'rate_limit_exceeded'
  if (status >= 500) return 'server_unavailable'
  if (status >= 400) return 'validation_error'
  return 'api_error'
}

/**
 * Create ToolingError from HTTP response
 *
 * Reads response body, parses error message, and creates appropriate ToolingError.
 * Supports custom error parser for API-specific response formats.
 *
 * @param response - Fetch Response object
 * @param context - Error context (integration name, method, etc.)
 * @param parseError - Optional custom parser for API-specific error formats
 * @returns ToolingError with appropriate type and message
 *
 * @example
 * // Basic usage
 * if (!response.ok) {
 *   throw await createHttpError(response, {
 *     integration: 'attio',
 *     method: 'listRecords',
 *     listId
 *   })
 * }
 *
 * @example
 * // With custom error parser
 * if (!response.ok) {
 *   throw await createHttpError(
 *     response,
 *     { integration: 'attio', method: 'createRecord' },
 *     (body) => {
 *       const json = JSON.parse(body)
 *       return json.message
 *     }
 *   )
 * }
 */
export async function createHttpError(
  response: Response,
  context: HttpErrorContext,
  parseError?: ErrorParser
): Promise<ToolingError> {
  const errorText = await response.text()
  let message = `${context.integration} API error: ${response.status} ${response.statusText}`

  // Try custom parser first, then generic JSON parsing
  if (parseError) {
    try {
      const parsed = parseError(errorText)
      if (parsed) message = parsed
    } catch {
      // Custom parser failed, fall through to generic parsing
    }
  }

  // Generic JSON parsing if no custom parser or custom parser didn't return a message
  if (!parseError || message.includes('API error:')) {
    try {
      const json = JSON.parse(errorText)
      const parsedMessage = json.message || json.error?.message
      if (parsedMessage) message = parsedMessage
    } catch {
      // Response is not JSON, use raw text if available
      if (errorText && errorText.length < 500) {
        message = errorText
      }
    }
  }

  return new ToolingError(
    mapHttpStatusToErrorType(response.status),
    message,
    { ...context, statusCode: response.status }
  )
}
