/**
 * Unified Signer Service Implementation
 *
 * Provides a unified interface for signing operations across all platforms
 * with automatic platform detection and strategy selection.
 */

import { Platform, ISignerStrategy, Credentials, SignResult } from '@/types/credential'
import { PacificaSigner } from './signers/PacificaSigner'
import { AsterSigner } from './signers/AsterSigner'
import { BinanceSigner } from './signers/BinanceSigner'
import { PlatformDetector } from './PlatformDetector'

// ============================================================================
// Types and Interfaces
// ============================================================================

export interface SigningOptions {
  /**
   * Override automatic platform detection
   */
  forcePlatform?: Platform

  /**
   * Timeout for signing operation (ms)
   */
  timeout?: number

  /**
   * Enable performance metrics collection
   */
  collectMetrics?: boolean

  /**
   * Additional context for signing operation
   */
  context?: Record<string, any>
}

export interface BatchSignRequest {
  accountId: string
  message: Uint8Array
  options?: SigningOptions
}

export interface BatchSignResult extends SignResult {
  accountId: string
  batchIndex: number
}

export interface SigningMetrics {
  totalOperations: number
  successfulOperations: number
  failedOperations: number
  averageSigningTime: number
  platformBreakdown: Record<Platform, {
    operations: number
    successRate: number
    averageTime: number
  }>
  lastResetAt: Date
}

// ============================================================================
// Main Implementation
// ============================================================================

export class UnifiedSigner {
  private strategies: Map<Platform, ISignerStrategy> = new Map()
  private platformDetector: PlatformDetector
  private metrics: SigningMetrics
  private getAccountFunction?: (accountId: string) => Promise<any>

  constructor() {
    this.platformDetector = new PlatformDetector()
    this.initializeStrategies()
    this.resetMetrics()
  }

  // ============================================================================
  // Initialization and Configuration
  // ============================================================================

  /**
   * Set function to retrieve account information
   */
  setAccountGetter(getAccountFn: (accountId: string) => Promise<any>): void {
    this.getAccountFunction = getAccountFn
  }

  /**
   * Register custom signer strategy
   */
  registerStrategy(platform: Platform, strategy: ISignerStrategy): void {
    if (!platform || !strategy) {
      throw new Error('Platform and strategy are required')
    }

    if (strategy.platform !== platform) {
      throw new Error(`Strategy platform mismatch: expected ${platform}, got ${strategy.platform}`)
    }

    this.strategies.set(platform, strategy)
  }

  /**
   * Get registered platforms
   */
  getSupportedPlatforms(): Platform[] {
    return Array.from(this.strategies.keys())
  }

  // ============================================================================
  // Core Signing Operations
  // ============================================================================

  /**
   * Sign message with unified interface
   */
  async sign(accountId: string, message: Uint8Array, options?: SigningOptions): Promise<SignResult> {
    const startTime = Date.now()

    try {
      // Get account information
      const account = await this.getAccount(accountId)
      if (!account) {
        return this.createFailureResult(startTime, `Account not found: ${accountId}`)
      }

      // Determine platform
      const platform = options?.forcePlatform || this.detectPlatform(account)
      if (!platform) {
        return this.createFailureResult(startTime, `Cannot determine platform for account: ${accountId}`)
      }

      // Get appropriate strategy
      const strategy = this.getStrategy(platform)
      if (!strategy) {
        return this.createFailureResult(startTime, `No strategy available for platform: ${platform}`)
      }

      // Perform signing with timeout
      const signature = await this.executeWithTimeout(
        () => strategy.sign(message, account.credentials),
        options?.timeout || 30000
      )

      const duration = Date.now() - startTime
      const result: SignResult = {
        success: true,
        signature,
        algorithm: strategy.algorithm,
        timestamp: new Date(),
        metadata: {
          platform,
          accountId,
          duration,
          ...(options?.context || {})
        }
      }

      // Update metrics
      if (options?.collectMetrics !== false) {
        this.updateMetrics(platform, true, duration)
      }

      return result

    } catch (error) {
      const duration = Date.now() - startTime
      const result = this.createFailureResult(
        startTime,
        error instanceof Error ? error.message : 'Unknown signing error'
      )

      // Update metrics for failure
      if (options?.collectMetrics !== false) {
        const platform = options?.forcePlatform || Platform.PACIFICA // Default for metrics
        this.updateMetrics(platform, false, duration)
      }

      return result
    }
  }

  /**
   * Verify signature with unified interface
   */
  async verify(
    accountId: string,
    message: Uint8Array,
    signature: string,
    options?: SigningOptions
  ): Promise<boolean> {
    try {
      // Get account information
      const account = await this.getAccount(accountId)
      if (!account) {
        throw new Error(`Account not found: ${accountId}`)
      }

      // Determine platform
      const platform = options?.forcePlatform || this.detectPlatform(account)
      if (!platform) {
        throw new Error(`Cannot determine platform for account: ${accountId}`)
      }

      // Get appropriate strategy
      const strategy = this.getStrategy(platform)
      if (!strategy) {
        throw new Error(`No strategy available for platform: ${platform}`)
      }

      // For verification, we need the public key
      // This is platform-specific implementation detail
      let publicKey: string

      if (platform === Platform.PACIFICA && strategy instanceof PacificaSigner) {
        publicKey = await strategy.derivePublicKey(account.credentials.privateKey)
      } else {
        // For other platforms, public key might be stored differently
        // This is a simplified implementation
        publicKey = account.credentials.publicKey || ''
      }

      // Perform verification with timeout
      return await this.executeWithTimeout(
        () => strategy.verify(message, signature, publicKey),
        options?.timeout || 30000
      )

    } catch (error) {
      console.error('Verification failed:', error instanceof Error ? error.message : 'Unknown error')
      return false
    }
  }

  // ============================================================================
  // Batch Operations
  // ============================================================================

  /**
   * Sign multiple messages in batch
   */
  async signBatch(requests: BatchSignRequest[]): Promise<BatchSignResult[]> {
    const results: BatchSignResult[] = []

    // Group requests by platform for optimal processing
    const platformGroups = this.groupRequestsByPlatform(requests)

    for (const [platform, platformRequests] of Object.entries(platformGroups)) {
      const strategy = this.getStrategy(platform as Platform)

      if (!strategy) {
        // Create failure results for unsupported platform
        platformRequests.forEach((req, index) => {
          results.push({
            ...this.createFailureResult(Date.now(), `No strategy for platform: ${platform}`),
            accountId: req.accountId,
            batchIndex: req.batchIndex
          })
        })
        continue
      }

      // Process requests for this platform
      const platformResults = await this.processPlatformBatch(strategy, platformRequests)
      results.push(...platformResults)
    }

    // Sort results by original batch index
    return results.sort((a, b) => a.batchIndex - b.batchIndex)
  }

  // ============================================================================
  // Performance Monitoring
  // ============================================================================

  /**
   * Get current performance metrics
   */
  getMetrics(): SigningMetrics {
    return {
      ...this.metrics,
      platformBreakdown: { ...this.metrics.platformBreakdown }
    }
  }

  /**
   * Reset performance metrics
   */
  resetMetrics(): void {
    this.metrics = {
      totalOperations: 0,
      successfulOperations: 0,
      failedOperations: 0,
      averageSigningTime: 0,
      platformBreakdown: {
        [Platform.PACIFICA]: { operations: 0, successRate: 0, averageTime: 0 },
        [Platform.ASTER]: { operations: 0, successRate: 0, averageTime: 0 },
        [Platform.BINANCE]: { operations: 0, successRate: 0, averageTime: 0 }
      },
      lastResetAt: new Date()
    }
  }

  // ============================================================================
  // Private Helper Methods
  // ============================================================================

  private initializeStrategies(): void {
    this.strategies.set(Platform.PACIFICA, new PacificaSigner())
    this.strategies.set(Platform.ASTER, new AsterSigner())
    this.strategies.set(Platform.BINANCE, new BinanceSigner())
  }

  private async getAccount(accountId: string): Promise<any> {
    if (!this.getAccountFunction) {
      throw new Error('Account getter function not configured')
    }
    return await this.getAccountFunction(accountId)
  }

  private detectPlatform(account: any): Platform | null {
    return this.platformDetector.detectPlatform(account.credentials)
  }

  private getStrategy(platform: Platform): ISignerStrategy | null {
    return this.strategies.get(platform) || null
  }

  private async executeWithTimeout<T>(operation: () => Promise<T>, timeoutMs: number): Promise<T> {
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        reject(new Error(`Operation timed out after ${timeoutMs}ms`))
      }, timeoutMs)

      operation()
        .then(result => {
          clearTimeout(timeout)
          resolve(result)
        })
        .catch(error => {
          clearTimeout(timeout)
          reject(error)
        })
    })
  }

  private createFailureResult(startTime: number, error: string): SignResult {
    return {
      success: false,
      algorithm: 'unknown',
      timestamp: new Date(),
      error,
      metadata: {
        duration: Date.now() - startTime
      }
    }
  }

  private updateMetrics(platform: Platform, success: boolean, duration: number): void {
    this.metrics.totalOperations++

    if (success) {
      this.metrics.successfulOperations++
    } else {
      this.metrics.failedOperations++
    }

    // Update average signing time
    const totalOps = this.metrics.totalOperations
    this.metrics.averageSigningTime =
      (this.metrics.averageSigningTime * (totalOps - 1) + duration) / totalOps

    // Update platform-specific metrics
    const platformStats = this.metrics.platformBreakdown[platform]
    platformStats.operations++

    if (success) {
      platformStats.successRate =
        (platformStats.successRate * (platformStats.operations - 1) + 1) / platformStats.operations
    } else {
      platformStats.successRate =
        (platformStats.successRate * (platformStats.operations - 1)) / platformStats.operations
    }

    platformStats.averageTime =
      (platformStats.averageTime * (platformStats.operations - 1) + duration) / platformStats.operations
  }

  private groupRequestsByPlatform(requests: BatchSignRequest[]): Record<string, Array<BatchSignRequest & { batchIndex: number }>> {
    const groups: Record<string, Array<BatchSignRequest & { batchIndex: number }>> = {}

    requests.forEach((request, index) => {
      // For batch processing, we'll need to detect platform per request
      // This is a simplified implementation
      const platform = request.options?.forcePlatform || Platform.PACIFICA

      if (!groups[platform]) {
        groups[platform] = []
      }

      groups[platform].push({
        ...request,
        batchIndex: index
      })
    })

    return groups
  }

  private async processPlatformBatch(
    strategy: ISignerStrategy,
    requests: Array<BatchSignRequest & { batchIndex: number }>
  ): Promise<BatchSignResult[]> {
    const results: BatchSignResult[] = []

    // Process requests sequentially for now
    // Could be optimized for parallel processing if strategy supports it
    for (const request of requests) {
      try {
        const account = await this.getAccount(request.accountId)
        if (!account) {
          results.push({
            ...this.createFailureResult(Date.now(), `Account not found: ${request.accountId}`),
            accountId: request.accountId,
            batchIndex: request.batchIndex
          })
          continue
        }

        const startTime = Date.now()
        const signature = await strategy.sign(request.message, account.credentials)
        const duration = Date.now() - startTime

        results.push({
          success: true,
          signature,
          algorithm: strategy.algorithm,
          timestamp: new Date(),
          accountId: request.accountId,
          batchIndex: request.batchIndex,
          metadata: {
            platform: strategy.platform,
            duration
          }
        })

      } catch (error) {
        results.push({
          ...this.createFailureResult(
            Date.now(),
            error instanceof Error ? error.message : 'Unknown error'
          ),
          accountId: request.accountId,
          batchIndex: request.batchIndex
        })
      }
    }

    return results
  }
}