/**
 * Compliance Client
 *
 * Main entry point for the SDK. Handles API communication with
 * retry logic, timeout handling, and optimistic locking.
 */

import type {
  APIResponse,
  StateResponse,
  CheckpointValidationResult,
  AuthTokenRequest,
  AuthTokenResponse,
  RAGSearchRequest,
  RAGSearchResponse,
  RAGAskRequest,
  RAGAskResponse,
  ExportFormat,
  SDKState,
  CheckpointStatus,
} from '@breakpilot/compliance-sdk-types'

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

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

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

// =============================================================================
// CONSTANTS
// =============================================================================

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

// =============================================================================
// COMPLIANCE CLIENT
// =============================================================================

export class ComplianceClient {
  private apiEndpoint: string
  private apiKey: string | null
  private tenantId: string
  private timeout: number
  private maxRetries: number
  private accessToken: string | null = null
  private abortControllers: Map<string, AbortController> = new Map()
  private onError?: (error: Error) => void
  private onAuthError?: () => void

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

  // ---------------------------------------------------------------------------
  // 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 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
  }

  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 = 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 this.createError(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 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)
        }

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

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

  // ---------------------------------------------------------------------------
  // Authentication
  // ---------------------------------------------------------------------------

  async authenticate(request: AuthTokenRequest): Promise<AuthTokenResponse> {
    const response = await this.fetchWithRetry<APIResponse<AuthTokenResponse>>(
      `${this.apiEndpoint}/auth/token`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(request),
      }
    )

    if (response.success && response.data) {
      this.accessToken = response.data.accessToken
      return response.data
    }

    throw this.createError(response.error || 'Authentication failed', 401, false)
  }

  async refreshToken(refreshToken: string): Promise<AuthTokenResponse> {
    return this.authenticate({
      grantType: 'refresh_token',
      clientId: '',
      refreshToken,
    })
  }

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

  clearAccessToken(): void {
    this.accessToken = null
  }

  // ---------------------------------------------------------------------------
  // State Management
  // ---------------------------------------------------------------------------

  async getState(): Promise<StateResponse | null> {
    try {
      const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
        `${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
        {
          method: 'GET',
          headers: this.getHeaders(),
        }
      )

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

      return null
    } catch (error) {
      const apiError = error as APIError
      if (apiError.status === 404) {
        return null
      }
      throw error
    }
  }

  async saveState(state: SDKState, version?: number): Promise<StateResponse> {
    const response = await this.fetchWithRetry<APIResponse<StateResponse>>(
      `${this.apiEndpoint}/state`,
      {
        method: 'POST',
        headers: {
          ...this.getHeaders(),
          ...(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!
  }

  async deleteState(): Promise<void> {
    await this.fetchWithRetry<APIResponse<void>>(
      `${this.apiEndpoint}/state?tenantId=${encodeURIComponent(this.tenantId)}`,
      {
        method: 'DELETE',
        headers: this.getHeaders(),
      }
    )
  }

  // ---------------------------------------------------------------------------
  // Checkpoints
  // ---------------------------------------------------------------------------

  async validateCheckpoint(
    checkpointId: string,
    data?: unknown
  ): Promise<CheckpointValidationResult> {
    const response = await this.fetchWithRetry<APIResponse<CheckpointValidationResult>>(
      `${this.apiEndpoint}/checkpoints/validate`,
      {
        method: 'POST',
        headers: this.getHeaders(),
        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
  }

  async getCheckpoints(): Promise<Record<string, CheckpointStatus>> {
    const response = await this.fetchWithRetry<APIResponse<Record<string, CheckpointStatus>>>(
      `${this.apiEndpoint}/checkpoints?tenantId=${encodeURIComponent(this.tenantId)}`,
      {
        method: 'GET',
        headers: this.getHeaders(),
      }
    )

    return response.data || {}
  }

  // ---------------------------------------------------------------------------
  // RAG
  // ---------------------------------------------------------------------------

  async searchRAG(request: RAGSearchRequest): Promise<RAGSearchResponse> {
    const response = await this.fetchWithRetry<APIResponse<RAGSearchResponse>>(
      `${this.apiEndpoint}/rag/search`,
      {
        method: 'POST',
        headers: this.getHeaders(),
        body: JSON.stringify(request),
      }
    )

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

    return response.data
  }

  async askRAG(request: RAGAskRequest): Promise<RAGAskResponse> {
    const response = await this.fetchWithRetry<APIResponse<RAGAskResponse>>(
      `${this.apiEndpoint}/rag/ask`,
      {
        method: 'POST',
        headers: this.getHeaders(),
        body: JSON.stringify(request),
      }
    )

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

    return response.data
  }

  // ---------------------------------------------------------------------------
  // Export
  // ---------------------------------------------------------------------------

  async exportState(format: ExportFormat): Promise<Blob> {
    const response = await this.fetchWithTimeout(
      `${this.apiEndpoint}/export?tenantId=${encodeURIComponent(this.tenantId)}&format=${format}`,
      {
        method: 'GET',
        headers: {
          ...this.getHeaders(),
          Accept:
            format === 'json'
              ? 'application/json'
              : format === 'pdf'
                ? 'application/pdf'
                : 'application/octet-stream',
        },
      },
      `export-${Date.now()}`
    )

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

    return response.blob()
  }

  // ---------------------------------------------------------------------------
  // Document Generation
  // ---------------------------------------------------------------------------

  async generateDocument(
    type: 'dsfa' | 'tom' | 'vvt' | 'gutachten' | 'privacy_policy' | 'cookie_banner',
    options?: Record<string, unknown>
  ): Promise<{ id: string; status: string; content?: string }> {
    const response = await this.fetchWithRetry<
      APIResponse<{ id: string; status: string; content?: string }>
    >(
      `${this.apiEndpoint}/generate/${type}`,
      {
        method: 'POST',
        headers: this.getHeaders(),
        body: JSON.stringify({
          tenantId: this.tenantId,
          options,
        }),
      }
    )

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

    return response.data
  }

  // ---------------------------------------------------------------------------
  // Security Scan
  // ---------------------------------------------------------------------------

  async startSecurityScan(options?: {
    tools?: string[]
    targetPath?: string
    severityThreshold?: string
    generateSBOM?: boolean
  }): Promise<{ id: string; status: string }> {
    const response = await this.fetchWithRetry<APIResponse<{ id: string; status: string }>>(
      `${this.apiEndpoint}/security/scan`,
      {
        method: 'POST',
        headers: this.getHeaders(),
        body: JSON.stringify({
          tenantId: this.tenantId,
          ...options,
        }),
      }
    )

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

    return response.data
  }

  async getSecurityScanResult(scanId: string): Promise<unknown> {
    const response = await this.fetchWithRetry<APIResponse<unknown>>(
      `${this.apiEndpoint}/security/scan/${scanId}`,
      {
        method: 'GET',
        headers: this.getHeaders(),
      }
    )

    return response.data
  }

  // ---------------------------------------------------------------------------
  // Utility
  // ---------------------------------------------------------------------------

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

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

  getTenantId(): string {
    return this.tenantId
  }

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

// =============================================================================
// FACTORY
// =============================================================================

let clientInstance: ComplianceClient | null = null

export function getComplianceClient(options?: ComplianceClientOptions): ComplianceClient {
  if (!clientInstance && !options) {
    throw new Error('ComplianceClient not initialized. Provide options on first call.')
  }

  if (!clientInstance && options) {
    clientInstance = new ComplianceClient(options)
  }

  return clientInstance!
}

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