/**
 * HTTP transport primitives for ComplianceClient.
 *
 * Phase 4: extracted from client.ts. Handles headers, timeouts, retry logic,
 * abort-controller lifecycle, and error classification so the main client
 * module can focus on domain endpoints.
 */

export interface HttpTransportOptions {
  apiEndpoint: string
  tenantId: string
  timeout?: number
  maxRetries?: number
  onAuthError?: () => void
  onError?: (error: Error) => void
}

export interface APIError extends Error {
  status?: number
  code?: string
  retryable: boolean
}

export const DEFAULT_TIMEOUT = 30000
export const DEFAULT_MAX_RETRIES = 3
const RETRY_DELAYS = [1000, 2000, 4000]

export function createHttpError(
  message: string,
  status?: number,
  retryable = false
): APIError {
  const error = new Error(message) as APIError
  error.status = status
  error.retryable = retryable
  return error
}

const sleep = (ms: number): Promise<void> =>
  new Promise(resolve => setTimeout(resolve, ms))

/**
 * HttpTransport — reusable fetch wrapper with retry + timeout + abort control.
 *
 * Not exposed via the SDK public surface; ComplianceClient instantiates one
 * internally.
 */
export class HttpTransport {
  readonly apiEndpoint: string
  private apiKey: string | null = null
  private accessToken: string | null = null
  private tenantId: string
  private timeout: number
  private maxRetries: number
  private abortControllers: Map<string, AbortController> = new Map()
  private onAuthError?: () => void
  private onError?: (error: Error) => void

  constructor(options: HttpTransportOptions) {
    this.apiEndpoint = options.apiEndpoint.replace(/\/$/, '')
    this.tenantId = options.tenantId
    this.timeout = options.timeout ?? DEFAULT_TIMEOUT
    this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES
    this.onAuthError = options.onAuthError
    this.onError = options.onError
  }

  setApiKey(apiKey: string | null): void {
    this.apiKey = apiKey
  }

  setAccessToken(token: string | null): void {
    this.accessToken = token
  }

  setTenantId(tenantId: string): void {
    this.tenantId = tenantId
  }

  getTenantId(): string {
    return this.tenantId
  }

  getHeaders(): Record<string, string> {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
      'X-Tenant-ID': this.tenantId,
    }

    if (this.accessToken) {
      headers['Authorization'] = `Bearer ${this.accessToken}`
    } else if (this.apiKey) {
      headers['Authorization'] = `Bearer ${this.apiKey}`
    }

    return headers
  }

  async fetchWithTimeout(
    url: string,
    options: RequestInit,
    requestId: string
  ): Promise<Response> {
    const controller = new AbortController()
    this.abortControllers.set(requestId, controller)

    const timeoutId = setTimeout(() => controller.abort(), this.timeout)

    try {
      const response = await fetch(url, {
        ...options,
        signal: controller.signal,
      })
      return response
    } finally {
      clearTimeout(timeoutId)
      this.abortControllers.delete(requestId)
    }
  }

  async fetchWithRetry<T>(
    url: string,
    options: RequestInit,
    retries = this.maxRetries
  ): Promise<T> {
    const requestId = `${Date.now()}-${Math.random()}`
    let lastError: Error | null = null

    for (let attempt = 0; attempt <= retries; attempt++) {
      try {
        const response = await this.fetchWithTimeout(url, options, requestId)

        if (!response.ok) {
          const errorBody = await response.text()
          let errorMessage = `HTTP ${response.status}`

          try {
            const errorJson = JSON.parse(errorBody)
            errorMessage = errorJson.error || errorJson.message || errorMessage
          } catch {
            // Keep HTTP status message
          }

          // Handle auth errors
          if (response.status === 401) {
            this.onAuthError?.()
            throw createHttpError(errorMessage, response.status, false)
          }

          // Don't retry client errors (4xx) except 429
          const retryable = response.status >= 500 || response.status === 429

          if (!retryable || attempt === retries) {
            throw createHttpError(errorMessage, response.status, retryable)
          }
        } else {
          const data = await response.json()
          return data as T
        }
      } catch (error) {
        lastError = error as Error

        if (error instanceof Error && error.name === 'AbortError') {
          throw createHttpError('Request timeout', 408, true)
        }

        const apiError = error as APIError
        if (!apiError.retryable || attempt === retries) {
          this.onError?.(error as Error)
          throw error
        }
      }

      // Exponential backoff
      if (attempt < retries) {
        await sleep(RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1])
      }
    }

    throw lastError || createHttpError('Unknown error', 500, false)
  }

  cancelAllRequests(): void {
    this.abortControllers.forEach(controller => controller.abort())
    this.abortControllers.clear()
  }
}
