/**
 * SDK API Client
 *
 * Centralized API client for SDK state management with error handling,
 * retry logic, and optimistic locking support.
 */

import { SDKState, CheckpointStatus } from './types'

// =============================================================================
// TYPES
// =============================================================================

export interface APIResponse<T> {
  success: boolean
  data?: T
  error?: string
  version?: number
  lastModified?: string
}

export interface StateResponse {
  tenantId: string
  state: SDKState
  version: number
  lastModified: string
}

export interface SaveStateRequest {
  tenantId: string
  state: SDKState
  version?: number // For optimistic locking
}

export interface CheckpointValidationResult {
  checkpointId: string
  passed: boolean
  errors: Array<{
    ruleId: string
    field: string
    message: string
    severity: 'ERROR' | 'WARNING' | 'INFO'
  }>
  warnings: Array<{
    ruleId: string
    field: string
    message: string
    severity: 'ERROR' | 'WARNING' | 'INFO'
  }>
  validatedAt: string
  validatedBy: string
}

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

// =============================================================================
// CONFIGURATION
// =============================================================================

const DEFAULT_BASE_URL = '/api/sdk/v1'
const DEFAULT_TIMEOUT = 30000 // 30 seconds
const MAX_RETRIES = 3
const RETRY_DELAYS = [1000, 2000, 4000] // Exponential backoff

// =============================================================================
// API CLIENT
// =============================================================================

export class SDKApiClient {
  private baseUrl: string
  private tenantId: string
  private timeout: number
  private abortControllers: Map<string, AbortController> = new Map()

  constructor(options: {
    baseUrl?: string
    tenantId: string
    timeout?: number
  }) {
    this.baseUrl = options.baseUrl || DEFAULT_BASE_URL
    this.tenantId = options.tenantId
    this.timeout = options.timeout || DEFAULT_TIMEOUT
  }

  // ---------------------------------------------------------------------------
  // Private Methods
  // ---------------------------------------------------------------------------

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

  private 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)
    }
  }

  private async fetchWithRetry<T>(
    url: string,
    options: RequestInit,
    retries = MAX_RETRIES
  ): 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 the HTTP status message
          }

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

          if (!retryable || attempt === retries) {
            throw this.createError(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 this.createError('Request timeout', 408, true)
        }

        // Check if it's a retryable error
        const apiError = error as APIError
        if (!apiError.retryable || attempt === retries) {
          throw error
        }
      }

      // Wait before retrying (exponential backoff)
      if (attempt < retries) {
        await this.sleep(RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1])
      }
    }

    throw lastError || this.createError('Unknown error', 500, false)
  }

  private sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  // ---------------------------------------------------------------------------
  // Public Methods - State Management
  // ---------------------------------------------------------------------------

  /**
   * Load SDK state for the current tenant
   */
  async getState(): Promise<StateResponse | null> {
    try {
      const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
        `${this.baseUrl}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        }
      )

      if (response.success && response.data) {
        return response.data
      }

      return null
    } catch (error) {
      const apiError = error as APIError
      // 404 means no state exists yet - that's okay
      if (apiError.status === 404) {
        return null
      }
      throw error
    }
  }

  /**
   * Save SDK state for the current tenant
   * Supports optimistic locking via version parameter
   */
  async saveState(state: SDKState, version?: number): Promise<StateResponse> {
    const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
      `${this.baseUrl}/state`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          ...(version !== undefined && { 'If-Match': String(version) }),
        },
        body: JSON.stringify({
          tenantId: this.tenantId,
          state,
          version,
        }),
      }
    )

    if (!response.success) {
      throw this.createError(response.error || 'Failed to save state', 500, true)
    }

    return response.data!
  }

  /**
   * Delete SDK state for the current tenant
   */
  async deleteState(): Promise<void> {
    await this.fetchWithRetry<APIResponse<void>>(
      `${this.baseUrl}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
      {
        method: 'DELETE',
        headers: {
          'Content-Type': 'application/json',
        },
      }
    )
  }

  // ---------------------------------------------------------------------------
  // Public Methods - Checkpoint Validation
  // ---------------------------------------------------------------------------

  /**
   * Validate a specific checkpoint
   */
  async validateCheckpoint(
    checkpointId: string,
    data?: unknown
  ): Promise<CheckpointValidationResult> {
    const response = await this.fetchWithRetry<APIResponse<CheckpointValidationResult>>(
      `${this.baseUrl}/checkpoints/validate`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          tenantId: this.tenantId,
          checkpointId,
          data,
        }),
      }
    )

    if (!response.success || !response.data) {
      throw this.createError(response.error || 'Checkpoint validation failed', 500, true)
    }

    return response.data
  }

  /**
   * Get all checkpoint statuses
   */
  async getCheckpoints(): Promise<Record<string, CheckpointStatus>> {
    const response = await this.fetchWithRetry<APIResponse<Record<string, CheckpointStatus>>>(
      `${this.baseUrl}/checkpoints?tenantId=${encodeURIComponent(this.tenantId)}`,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      }
    )

    return response.data || {}
  }

  // ---------------------------------------------------------------------------
  // Public Methods - Flow Navigation
  // ---------------------------------------------------------------------------

  /**
   * Get current flow state
   */
  async getFlowState(): Promise<{
    currentStep: string
    currentPhase: 1 | 2
    completedSteps: string[]
    suggestions: Array<{ stepId: string; reason: string }>
  }> {
    const response = await this.fetchWithRetry<APIResponse<{
      currentStep: string
      currentPhase: 1 | 2
      completedSteps: string[]
      suggestions: Array<{ stepId: string; reason: string }>
    }>>(
      `${this.baseUrl}/flow?tenantId=${encodeURIComponent(this.tenantId)}`,
      {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      }
    )

    if (!response.data) {
      throw this.createError('Failed to get flow state', 500, true)
    }

    return response.data
  }

  /**
   * Navigate to next/previous step
   */
  async navigateFlow(direction: 'next' | 'previous'): Promise<{
    stepId: string
    phase: 1 | 2
  }> {
    const response = await this.fetchWithRetry<APIResponse<{
      stepId: string
      phase: 1 | 2
    }>>(
      `${this.baseUrl}/flow`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          tenantId: this.tenantId,
          direction,
        }),
      }
    )

    if (!response.data) {
      throw this.createError('Failed to navigate flow', 500, true)
    }

    return response.data
  }

  // ---------------------------------------------------------------------------
  // Public Methods - Export
  // ---------------------------------------------------------------------------

  /**
   * Export SDK state in various formats
   */
  async exportState(format: 'json' | 'pdf' | 'zip'): Promise<Blob> {
    const response = await this.fetchWithTimeout(
      `${this.baseUrl}/export?tenantId=${encodeURIComponent(this.tenantId)}&format=${format}`,
      {
        method: 'GET',
        headers: {
          'Accept': format === 'json' ? 'application/json' : format === 'pdf' ? 'application/pdf' : 'application/zip',
        },
      },
      `export-${Date.now()}`
    )

    if (!response.ok) {
      throw this.createError(`Export failed: ${response.statusText}`, response.status, true)
    }

    return response.blob()
  }

  // ---------------------------------------------------------------------------
  // Public Methods - Utility
  // ---------------------------------------------------------------------------

  /**
   * Cancel all pending requests
   */
  cancelAllRequests(): void {
    this.abortControllers.forEach(controller => controller.abort())
    this.abortControllers.clear()
  }

  /**
   * Update tenant ID (useful when switching contexts)
   */
  setTenantId(tenantId: string): void {
    this.tenantId = tenantId
  }

  /**
   * Get current tenant ID
   */
  getTenantId(): string {
    return this.tenantId
  }

  /**
   * Health check
   */
  async healthCheck(): Promise<boolean> {
    try {
      const response = await this.fetchWithTimeout(
        `${this.baseUrl}/health`,
        { method: 'GET' },
        `health-${Date.now()}`
      )
      return response.ok
    } catch {
      return false
    }
  }
}

// =============================================================================
// SINGLETON FACTORY
// =============================================================================

let clientInstance: SDKApiClient | null = null

export function getSDKApiClient(tenantId?: string): SDKApiClient {
  if (!clientInstance && !tenantId) {
    throw new Error('SDKApiClient not initialized. Provide tenantId on first call.')
  }

  if (!clientInstance && tenantId) {
    clientInstance = new SDKApiClient({ tenantId })
  }

  if (tenantId && clientInstance && clientInstance.getTenantId() !== tenantId) {
    clientInstance.setTenantId(tenantId)
  }

  return clientInstance!
}

export function resetSDKApiClient(): void {
  if (clientInstance) {
    clientInstance.cancelAllRequests()
  }
  clientInstance = null
}
