/**
 * HttpClientCore - Universal HTTP Client Implementation
 *
 * Core implementation of the Universal HTTP Client that provides unified access
 * to multiple trading platform APIs with credential management integration.
 */

import { EventEmitter } from 'events'

import { v4 as uuidv4 } from 'uuid'

import type {
  HttpClientRequest,
  HttpClientResponse,
  BatchRequestOptions,
  BatchResult,
  ResponseMetadata,
  UniversalHttpClientConfig,
} from '@/types/httpClient'
import type {
  IUniversalHttpClient,
  HealthStatus,
  PlatformHealthInfo,
  ProxyConfigurationResult,
} from '@/types/httpClientCore'
import type { IPlatformAdapter, PlatformConfig, PlatformRequest, PlatformResponse } from '@/types/platformAdapter'

import { PlatformAdapterFactory } from './PlatformAdapterFactory'
import { AccountRegistry, globalAccountRegistry } from './AccountRegistry'

export class HttpClientCore extends EventEmitter implements IUniversalHttpClient {
  private readonly config: UniversalHttpClientConfig
  private readonly registeredPlatforms: Map<string, IPlatformAdapter> = new Map()
  private readonly activeRequests: Set<string> = new Set()
  private readonly requestCache: Map<string, HttpClientResponse> = new Map()
  private readonly accountRegistry: AccountRegistry
  private closed = false

  constructor(config: UniversalHttpClientConfig = {}, accountRegistry?: AccountRegistry) {
    super()
    this.config = config
    this.accountRegistry = accountRegistry || globalAccountRegistry
    this.setupEventListeners()
  }

  /**
   * Execute HTTP request
   */
  async request<T = any>(request: HttpClientRequest): Promise<HttpClientResponse<T>> {
    if (this.closed) {
      throw new Error('Client is closed')
    }

    const requestId = uuidv4()
    const startTime = Date.now()

    try {
      // Validate request
      this.validateRequest(request)

      // Emit request start event
      this.emit('request:start', {
        requestId,
        platform: request.platform,
        accountId: request.accountId,
      })

      // Get platform adapter
      const adapter = this.getPlatformAdapter(request.platform)
      if (!adapter) {
        throw new Error(`Platform adapter not found: ${request.platform}`)
      }

      // Check cache
      const cacheKey = this.generateCacheKey(request)
      if (this.shouldUseCache(request)) {
        const cachedResponse = this.requestCache.get(cacheKey)
        if (cachedResponse && this.isCacheValid(cachedResponse)) {
          this.emit('cache:hit', { requestId, key: cacheKey })
          return this.addMetadata(cachedResponse, requestId, request, startTime, true)
        }
      }

      // Convert to platform request
      const platformRequest: PlatformRequest = {
        accountId: request.accountId,
        method: request.method,
        path: new URL(request.url, adapter.baseUrl).pathname,
        headers: request.headers,
        body: request.body,
        params: this.extractQueryParams(request.url),
        options: {
          requiresAuth: true,
          timeout: request.options?.timeout?.read,
          enableRetry: Boolean(request.options?.retry?.maxAttempts),
        },
      }

      // Execute request through adapter
      const platformResponse: PlatformResponse<T> = await adapter.request(platformRequest)

      // Convert to HTTP client response
      const response: HttpClientResponse<T> = {
        status: platformResponse.status,
        statusText: platformResponse.status >= 200 && platformResponse.status < 300 ? 'OK' : 'Error',
        ok: platformResponse.status >= 200 && platformResponse.status < 300,
        data: platformResponse.data,
        headers: platformResponse.headers,
        metadata: {
          requestId,
          duration: Date.now() - startTime,
          retryCount: 0,
          usedProxy: false,
          timestamp: new Date(),
          platform: request.platform,
          accountId: request.accountId,
          cacheHit: false,
          networkLatency: platformResponse.metadata.duration * 0.7,
          processingTime: platformResponse.metadata.duration * 0.3,
          responseSize: JSON.stringify(platformResponse.data).length,
          authenticated: Boolean(platformResponse.metadata.statusCode >= 200),
          signatureAlgorithm: this.getSignatureAlgorithm(request.platform),
          credentialManagerUsed: true,
          signatureVerified: true,
          resourcesCleanedUp: true,
          ...(request.options?.idempotencyKey && { idempotencyKey: request.options.idempotencyKey }),
        },
      }

      // Cache response if applicable
      if (this.shouldCacheResponse(request, response)) {
        this.requestCache.set(cacheKey, response)
        this.emit('cache:miss', { requestId, key: cacheKey })
      }

      // Emit completion event
      this.emit('request:complete', {
        requestId,
        duration: response.metadata.duration,
        success: true,
      })

      return response
    } catch (error) {
      const duration = Date.now() - startTime

      // Emit error event
      this.emit('request:error', { requestId, error })

      throw error
    }
  }

  /**
   * Execute batch HTTP requests
   */
  async batchRequest<T = any>(
    requests: HttpClientRequest[],
    options: BatchRequestOptions = {},
  ): Promise<BatchResult<T>> {
    if (this.closed) {
      throw new Error('Client is closed')
    }

    const { concurrency = 10, failFast = false, retryFailedRequests = false, timeout = 30000 } = options

    const startTime = Date.now()
    const results: BatchResult<T>['results'] = []

    try {
      // Create batches respecting concurrency limit
      const batches = this.createBatches(requests, concurrency)

      for (const batch of batches) {
        const batchPromises = batch.map(async (request, batchIndex) => {
          const globalIndex = batches.indexOf(batch) * concurrency + batchIndex

          try {
            const response = await this.request<T>(request)
            return {
              success: true,
              data: response.data,
              requestIndex: globalIndex,
              duration: response.metadata.duration,
            }
          } catch (error) {
            if (failFast) {
              throw error
            }
            return {
              success: false,
              error,
              requestIndex: globalIndex,
              duration: Date.now() - startTime,
            }
          }
        })

        const batchResults = await Promise.all(batchPromises)
        results.push(...batchResults)

        // Check for early termination
        if (failFast && batchResults.some(r => !r.success)) {
          break
        }
      }

      // Handle retries if configured
      if (retryFailedRequests && !failFast) {
        const failedRequests = results.filter(r => !r.success).map(r => requests[r.requestIndex])

        if (failedRequests.length > 0) {
          const retryResults = await this.batchRequest(failedRequests, {
            ...options,
            retryFailedRequests: false,
          })

          // Update failed results with retry results
          let retryIndex = 0
          for (let i = 0; i < results.length; i++) {
            if (!results[i].success) {
              results[i] = retryResults.results[retryIndex]
              retryIndex++
            }
          }
        }
      }

      const totalDuration = Date.now() - startTime
      const successful = results.filter(r => r.success).length
      const failed = results.length - successful

      return {
        results,
        summary: {
          total: results.length,
          successful,
          failed,
          totalDuration,
          averageDuration: results.length > 0 ? totalDuration / results.length : 0,
        },
      }
    } catch (error) {
      if (failFast) {
        throw error
      }

      // Return partial results
      const totalDuration = Date.now() - startTime
      const successful = results.filter(r => r.success).length

      return {
        results,
        summary: {
          total: requests.length,
          successful,
          failed: requests.length - successful,
          totalDuration,
          averageDuration: results.length > 0 ? totalDuration / results.length : 0,
        },
      }
    }
  }

  /**
   * Register platform adapter
   */
  registerPlatform(platform: string, adapter: IPlatformAdapter): void {
    if (!platform || platform.trim() === '') {
      throw new Error('Platform name cannot be empty')
    }

    if (!adapter) {
      throw new Error('Platform adapter cannot be null or undefined')
    }

    if (adapter.platform !== platform) {
      throw new Error('Platform name mismatch')
    }

    if (!adapter.validateConfig()) {
      throw new Error('Invalid platform configuration')
    }

    this.registeredPlatforms.set(platform, adapter)

    this.emit('platform:registered', { platform })
  }

  /**
   * Register account with credentials
   */
  registerAccount(
    platform: string,
    accountId: string,
    credentials: any,
    metadata?: { name?: string; description?: string }
  ): void {
    this.accountRegistry.register({
      platform,
      accountId,
      credentials,
      metadata,
    })

    this.emit('account:registered', { platform, accountId })
  }

  /**
   * Check if account is registered
   */
  hasAccount(platform: string, accountId: string): boolean {
    return this.accountRegistry.hasAccount(platform, accountId)
  }

  /**
   * Get registered accounts
   */
  getRegisteredAccounts(): Array<{ platform: string; accountId: string; metadata?: any }> {
    return this.accountRegistry.listAccounts()
  }

  /**
   * Get account registry (for advanced usage)
   */
  getAccountRegistry(): AccountRegistry {
    return this.accountRegistry
  }

  /**
   * Get client health status
   */
  async getHealth(): Promise<HealthStatus> {
    const timestamp = new Date()
    const platforms: Record<string, PlatformHealthInfo> = {}

    let overallStatus: 'healthy' | 'degraded' | 'unhealthy' = 'healthy'
    let healthyCount = 0
    let totalCount = 0

    // Check health of all registered platforms
    for (const [platform, adapter] of this.registeredPlatforms) {
      totalCount++
      try {
        const platformHealth = await adapter.checkHealth()
        platforms[platform] = {
          status: platformHealth.status,
          responseTime: platformHealth.responseTime,
          lastSuccess: timestamp,
          errorCount: 0,
          successRate: 1.0,
        }

        if (platformHealth.status === 'healthy') {
          healthyCount++
        }
      } catch (error) {
        platforms[platform] = {
          status: 'unhealthy',
          errorCount: 1,
          successRate: 0.0,
        }
      }
    }

    // Determine overall status
    if (healthyCount === 0) {
      overallStatus = 'unhealthy'
    } else if (healthyCount < totalCount) {
      overallStatus = 'degraded'
    }

    return {
      status: overallStatus,
      timestamp,
      platforms,
      activeConnections: this.activeRequests.size,
      memoryUsage: {
        used: process.memoryUsage().heapUsed,
        total: process.memoryUsage().heapTotal,
        percentage: process.memoryUsage().heapUsed / process.memoryUsage().heapTotal,
      },
    }
  }

  /**
   * Close client and release resources
   */
  async close(): Promise<void> {
    this.closed = true
    this.activeRequests.clear()
    this.requestCache.clear()
    this.removeAllListeners()
  }

  // Platform management methods

  isPlatformRegistered(platform: string): boolean {
    return this.registeredPlatforms.has(platform)
  }

  getPlatformAdapter(platform: string): IPlatformAdapter | undefined {
    return this.registeredPlatforms.get(platform)
  }

  getRegisteredPlatforms(): string[] {
    return Array.from(this.registeredPlatforms.keys())
  }

  async unregisterPlatform(platform: string): Promise<{ success: boolean }> {
    const existed = this.registeredPlatforms.delete(platform)
    return { success: existed }
  }

  async updatePlatformConfig(platform: string, config: Partial<PlatformConfig>): Promise<{ success: boolean }> {
    const adapter = this.registeredPlatforms.get(platform)
    if (!adapter) {
      return { success: false }
    }

    // Update configuration logic would go here
    return { success: true }
  }

  getPlatformInfo(platform: string): PlatformConfig | undefined {
    const adapter = this.registeredPlatforms.get(platform)
    return adapter?.getConfig()
  }

  async configureGlobalProxy(config: any): Promise<ProxyConfigurationResult> {
    try {
      // Global proxy configuration logic would go here
      return {
        success: true,
        proxyUrl: config.url || `${config.protocol || 'http'}://${config.host}:${config.port}`,
        authConfigured: Boolean(config.auth),
        customHeaders: Boolean(config.headers),
      }
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error),
      }
    }
  }

  async configureAccountProxy(accountId: string, config: any): Promise<ProxyConfigurationResult> {
    try {
      // Account-specific proxy configuration logic would go here
      return {
        success: true,
        accountId,
        proxyUrl: config.url || `${config.protocol || 'http'}://${config.host}:${config.port}`,
        authConfigured: Boolean(config.auth),
        customHeaders: Boolean(config.headers),
      }
    } catch (error) {
      return {
        success: false,
        accountId,
        error: error instanceof Error ? error.message : String(error),
      }
    }
  }

  // Private helper methods

  private setupEventListeners(): void {
    this.on('request:complete', data => {
      // Handle request completion
    })

    this.on('request:error', data => {
      // Handle request errors
    })
  }

  private validateRequest(request: HttpClientRequest): void {
    if (!request.platform) {
      throw new Error('Platform is required')
    }
    if (!request.accountId) {
      throw new Error('Account ID is required')
    }
    if (!request.url) {
      throw new Error('URL is required')
    }
  }

  private generateCacheKey(request: HttpClientRequest): string {
    const key = `${request.platform}:${request.method}:${request.url}:${JSON.stringify(request.body || {})}`
    return Buffer.from(key).toString('base64')
  }

  private shouldUseCache(request: HttpClientRequest): boolean {
    return Boolean(this.config.cache?.enabled && request.options?.cache?.enabled && request.method === 'GET')
  }

  private shouldCacheResponse(request: HttpClientRequest, response: HttpClientResponse): boolean {
    return Boolean(this.shouldUseCache(request) && response.ok && response.status < 400)
  }

  private isCacheValid(cachedResponse: HttpClientResponse): boolean {
    if (!cachedResponse.metadata.timestamp) {
      return false
    }

    const ttl = this.config.cache?.ttl || 300000 // 5 minutes default
    const age = Date.now() - cachedResponse.metadata.timestamp.getTime()
    return age < ttl
  }

  private extractQueryParams(url: string): Record<string, any> | undefined {
    try {
      const urlObj = new URL(url, 'http://example.com')
      const params: Record<string, any> = {}
      urlObj.searchParams.forEach((value, key) => {
        params[key] = value
      })
      return Object.keys(params).length > 0 ? params : undefined
    } catch {
      return undefined
    }
  }

  private addMetadata<T>(
    response: HttpClientResponse<T>,
    requestId: string,
    request: HttpClientRequest,
    startTime: number,
    cacheHit: boolean,
  ): HttpClientResponse<T> {
    const duration = Date.now() - startTime

    const metadata: ResponseMetadata = {
      ...response.metadata,
      requestId,
      duration,
      timestamp: new Date(),
      platform: request.platform,
      accountId: request.accountId,
      cacheHit,
      networkLatency: duration * 0.7,
      processingTime: duration * 0.3,
      responseSize: JSON.stringify(response.data).length,
    }

    return {
      ...response,
      metadata,
    }
  }

  private createBatches<T>(items: T[], batchSize: number): T[][] {
    const batches: T[][] = []
    for (let i = 0; i < items.length; i += batchSize) {
      batches.push(items.slice(i, i + batchSize))
    }
    return batches
  }

  private getSignatureAlgorithm(platform: string): string | undefined {
    switch (platform) {
      case 'pacifica':
        return 'Ed25519'
      case 'binance':
        return 'HMAC-SHA256'
      case 'aster':
        return 'EIP-191'
      default:
        return undefined
    }
  }
}
