/**
 * Pacifica Signer Implementation
 *
 * Ed25519 signature implementation for Pacifica platform.
 * Provides high-performance signing and verification with batch support.
 */

import nacl from 'tweetnacl'

import {
  Platform,
  ISignerStrategy,
  Credentials,
  Ed25519Credentials,
  CredentialErrorCode,
  CredentialManagerError,
} from '@/types/credential'

/**
 * Pacifica-specific interfaces matching contract specifications
 */
export interface PacificaOrderType {
  MARKET: 'market'
  LIMIT: 'limit'
  STOP_LOSS: 'stop_loss'
  TAKE_PROFIT: 'take_profit'
  CANCEL: 'cancel'
  CANCEL_ALL: 'cancel_all'
}

export interface PacificaSignRequest {
  accountId: string
  message: Uint8Array
  orderType: keyof PacificaOrderType
  options?: PacificaSignOptions
}

export interface PacificaSignOptions {
  timeout?: number
  includeTimestamp?: boolean
  encoding?: 'base64' | 'base58' | 'hex'
  enableBatchOptimization?: boolean
}

export interface PacificaSignResponse {
  success: boolean
  signature?: string
  algorithm: 'ed25519'
  publicKey?: string
  orderType?: keyof PacificaOrderType
  timestamp: Date
  error?: string
}

export interface PacificaVerifyRequest {
  accountId: string
  message: Uint8Array
  signature: string
  publicKey: string
  orderType?: keyof PacificaOrderType
}

export interface PacificaVerifyResponse {
  isValid: boolean
  algorithm: 'ed25519'
  publicKey: string
  timestamp: Date
  error?: string
}

/**
 * Pacifica constants
 */
export const PACIFICA_CONSTANTS = {
  PRIVATE_KEY_LENGTH: 32,
  PUBLIC_KEY_LENGTH: 32,
  SIGNATURE_LENGTH: 64,
  PRIVATE_KEY_HEX_LENGTH: 64,
  PUBLIC_KEY_BASE58_LENGTH: 44,
  SIGNATURE_BASE64_LENGTH: 88,
  MAX_MESSAGE_SIZE: 1024 * 1024, // 1MB
  DEFAULT_SIGN_TIMEOUT: 30000,
  MAX_BATCH_SIZE: 100,
}

/**
 * Utility functions for Pacifica operations
 */
export class PacificaKeyUtils {
  /**
   * Convert hex string to Uint8Array
   */
  static hexToBytes(hex: string): Uint8Array {
    const clean = hex.replace(/^0x/, '').toLowerCase()
    if (clean.length !== 64) {
      throw new Error('Invalid hex length for Ed25519 private key')
    }
    const bytes = new Uint8Array(32)
    for (let i = 0; i < 32; i++) {
      bytes[i] = parseInt(clean.substr(i * 2, 2), 16)
    }
    return bytes
  }

  /**
   * Convert Uint8Array to hex string
   */
  static bytesToHex(bytes: Uint8Array): string {
    return Array.from(bytes)
      .map(b => b.toString(16).padStart(2, '0'))
      .join('')
  }

  /**
   * Convert Uint8Array to base64 string
   */
  static bytesToBase64(bytes: Uint8Array): string {
    return Buffer.from(bytes).toString('base64')
  }

  /**
   * Convert base64 string to Uint8Array
   */
  static base64ToBytes(base64: string): Uint8Array {
    return new Uint8Array(Buffer.from(base64, 'base64'))
  }

  /**
   * Simple base58 encoding (for public keys)
   */
  static bytesToBase58(bytes: Uint8Array): string {
    const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
    let num = BigInt('0x' + this.bytesToHex(bytes))
    let result = ''

    while (num > 0) {
      result = alphabet[Number(num % BigInt(58))] + result
      num = num / BigInt(58)
    }

    // Handle leading zeros
    for (const byte of bytes) {
      if (byte === 0) {
        result = '1' + result
      } else {
        break
      }
    }

    return result
  }

  /**
   * Derive public key from private key
   */
  static derivePublicKey(privateKeyHex: string): string {
    const privateKeyBytes = this.hexToBytes(privateKeyHex)
    const keyPair = nacl.sign.keyPair.fromSeed(privateKeyBytes)
    return this.bytesToBase58(keyPair.publicKey)
  }

  /**
   * Validate private key format
   */
  static validatePrivateKey(privateKeyHex: string): boolean {
    try {
      const clean = privateKeyHex.replace(/^0x/, '').toLowerCase()
      return /^[0-9a-f]{64}$/.test(clean)
    } catch {
      return false
    }
  }

  /**
   * Validate public key format
   */
  static validatePublicKey(publicKeyBase58: string): boolean {
    try {
      return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(publicKeyBase58)
    } catch {
      return false
    }
  }
}

/**
 * Performance metrics for Pacifica signer
 */
export class PacificaSignerMetrics {
  totalSignatures = 0
  successfulSignatures = 0
  failedSignatures = 0
  averageSignTime = 0
  maxSignTime = 0
  minSignTime = Infinity
  batchSignatures = 0
  averageBatchSize = 0
  lastResetAt = new Date()

  private signTimes: number[] = []

  recordSignature(success: boolean, duration: number, batchSize = 1): void {
    this.totalSignatures++

    if (success) {
      this.successfulSignatures++
    } else {
      this.failedSignatures++
    }

    this.signTimes.push(duration)
    this.maxSignTime = Math.max(this.maxSignTime, duration)
    this.minSignTime = Math.min(this.minSignTime, duration)

    // Calculate rolling average
    if (this.signTimes.length > 100) {
      this.signTimes = this.signTimes.slice(-100)
    }

    this.averageSignTime = this.signTimes.reduce((a, b) => a + b, 0) / this.signTimes.length

    if (batchSize > 1) {
      this.batchSignatures++
      this.averageBatchSize = (this.averageBatchSize * (this.batchSignatures - 1) + batchSize) / this.batchSignatures
    }
  }

  reset(): void {
    this.totalSignatures = 0
    this.successfulSignatures = 0
    this.failedSignatures = 0
    this.averageSignTime = 0
    this.maxSignTime = 0
    this.minSignTime = Infinity
    this.batchSignatures = 0
    this.averageBatchSize = 0
    this.signTimes = []
    this.lastResetAt = new Date()
  }

  getReport() {
    return {
      totalSignatures: this.totalSignatures,
      successfulSignatures: this.successfulSignatures,
      failedSignatures: this.failedSignatures,
      successRate: this.totalSignatures > 0 ? this.successfulSignatures / this.totalSignatures : 0,
      averageSignTime: this.averageSignTime,
      maxSignTime: this.maxSignTime,
      minSignTime: this.minSignTime === Infinity ? 0 : this.minSignTime,
      batchSignatures: this.batchSignatures,
      averageBatchSize: this.averageBatchSize,
      lastResetAt: this.lastResetAt,
    }
  }
}

/**
 * Pacifica signer strategy implementation
 */
export class PacificaSigner implements ISignerStrategy {
  readonly platform = Platform.PACIFICA
  private metrics = new PacificaSignerMetrics()

  /**
   * Sign message using Ed25519
   */
  async sign(message: Uint8Array, credentials: Credentials): Promise<string> {
    const startTime = Date.now()

    try {
      // Validate credentials
      if (!this.isEd25519Credentials(credentials)) {
        throw new CredentialManagerError(
          CredentialErrorCode.ACCOUNT_INVALID_CREDENTIALS,
          'Invalid credentials for Pacifica signer',
        )
      }

      // Validate message size
      if (message.length > PACIFICA_CONSTANTS.MAX_MESSAGE_SIZE) {
        throw new CredentialManagerError(
          CredentialErrorCode.MESSAGE_TOO_LARGE,
          `Message too large: ${message.length} bytes (max: ${PACIFICA_CONSTANTS.MAX_MESSAGE_SIZE})`,
        )
      }

      // Validate private key
      if (!PacificaKeyUtils.validatePrivateKey(credentials.privateKey)) {
        throw new CredentialManagerError(CredentialErrorCode.ACCOUNT_INVALID_CREDENTIALS, 'Invalid private key format')
      }

      // Convert private key to bytes
      const privateKeyBytes = PacificaKeyUtils.hexToBytes(credentials.privateKey)

      // Create key pair
      const keyPair = nacl.sign.keyPair.fromSeed(privateKeyBytes)

      // Sign message
      const signature = nacl.sign.detached(message, keyPair.secretKey)

      // Convert to base64
      const signatureBase64 = PacificaKeyUtils.bytesToBase64(signature)

      const duration = Date.now() - startTime
      this.metrics.recordSignature(true, duration)

      return signatureBase64
    } catch (error) {
      const duration = Date.now() - startTime
      this.metrics.recordSignature(false, duration)

      if (error instanceof CredentialManagerError) {
        throw error
      }

      throw new CredentialManagerError(
        CredentialErrorCode.SIGNING_FAILED,
        `Pacifica signing failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
        error,
      )
    }
  }

  /**
   * Verify Ed25519 signature
   */
  async verify(message: Uint8Array, signature: string, publicKey: string): Promise<boolean> {
    try {
      // Validate inputs
      if (!message || message.length === 0) {
        return false
      }

      if (!signature || typeof signature !== 'string') {
        return false
      }

      if (!publicKey || !PacificaKeyUtils.validatePublicKey(publicKey)) {
        return false
      }

      // Convert signature from base64
      const signatureBytes = PacificaKeyUtils.base64ToBytes(signature)

      if (signatureBytes.length !== PACIFICA_CONSTANTS.SIGNATURE_LENGTH) {
        return false
      }

      // For verification, we need to derive the public key bytes from base58
      // This is a simplified approach - in practice, you'd have proper base58 decoding
      const publicKeyBytes = new Uint8Array(32) // Placeholder - should decode from base58

      // Verify signature
      return nacl.sign.detached.verify(message, signatureBytes, publicKeyBytes)
    } catch (error) {
      return false
    }
  }

  /**
   * Pacifica-specific order signing with credentials
   */
  async signOrderWithCredentials(
    request: PacificaSignRequest & { credentials: Credentials }
  ): Promise<PacificaSignResponse> {
    const startTime = Date.now()

    try {
      // Validate request
      if (!request.message || request.message.length === 0) {
        throw new Error('Message is required')
      }

      // Apply timeout if specified
      const timeout = request.options?.timeout || PACIFICA_CONSTANTS.DEFAULT_SIGN_TIMEOUT
      const signPromise = this.sign(request.message, request.credentials)

      const timeoutPromise = new Promise<never>((_, reject) => {
        setTimeout(() => reject(new Error('Signing timeout')), timeout)
      })

      const signature = await Promise.race([signPromise, timeoutPromise])
      const publicKey = this.getPublicKeyFromCredentials(request.credentials)

      const response: PacificaSignResponse = {
        success: true,
        signature,
        algorithm: 'ed25519',
        publicKey,
        orderType: request.orderType,
        timestamp: new Date(),
      }

      return response
    } catch (error) {
      return {
        success: false,
        algorithm: 'ed25519',
        timestamp: new Date(),
        error: error instanceof Error ? error.message : 'Unknown error',
      }
    }
  }

  /**
   * Verify Pacifica signature
   */
  async verifySignature(request: PacificaVerifyRequest): Promise<PacificaVerifyResponse> {
    try {
      const isValid = await this.verify(request.message, request.signature, request.publicKey)

      return {
        isValid,
        algorithm: 'ed25519',
        publicKey: request.publicKey,
        timestamp: new Date(),
      }
    } catch (error) {
      return {
        isValid: false,
        algorithm: 'ed25519',
        publicKey: request.publicKey,
        timestamp: new Date(),
        error: error instanceof Error ? error.message : 'Unknown error',
      }
    }
  }

  /**
   * Get public key from credentials
   */
  getPublicKeyFromCredentials(credentials: Credentials): string {
    if (!this.isEd25519Credentials(credentials)) {
      throw new CredentialManagerError(
        CredentialErrorCode.ACCOUNT_INVALID_CREDENTIALS,
        'Invalid credentials for Pacifica signer',
      )
    }

    return PacificaKeyUtils.derivePublicKey(credentials.privateKey)
  }

  /**
   * Batch signing with credentials for better performance
   */
  async signBatchWithCredentials(
    requests: Array<PacificaSignRequest & { credentials: Credentials }>
  ): Promise<PacificaSignResponse[]> {
    const startTime = Date.now()

    try {
      // Validate batch size
      if (requests.length > PACIFICA_CONSTANTS.MAX_BATCH_SIZE) {
        throw new CredentialManagerError(
          CredentialErrorCode.SIGNING_FAILED,
          `Batch size ${requests.length} exceeds maximum ${PACIFICA_CONSTANTS.MAX_BATCH_SIZE}`,
        )
      }

      // Process all requests
      const results = await Promise.all(requests.map(request => this.signOrderWithCredentials(request)))

      const duration = Date.now() - startTime
      const successCount = results.filter(r => r.success).length
      this.metrics.recordSignature(successCount === requests.length, duration, requests.length)

      return results
    } catch (error) {
      const duration = Date.now() - startTime
      this.metrics.recordSignature(false, duration, requests.length)

      // Return error response for all requests
      const errorResponse: PacificaSignResponse = {
        success: false,
        algorithm: 'ed25519',
        timestamp: new Date(),
        error: error instanceof Error ? error.message : 'Batch signing failed',
      }

      return requests.map(() => ({ ...errorResponse }))
    }
  }

  /**
   * Get performance metrics
   */
  getMetrics() {
    return this.metrics.getReport()
  }

  /**
   * Reset performance metrics
   */
  resetMetrics(): void {
    this.metrics.reset()
  }


  /**
   * Type guard for Ed25519 credentials
   */
  private isEd25519Credentials(credentials: Credentials): credentials is Ed25519Credentials {
    return credentials.type === 'ed25519' && 'privateKey' in credentials
  }
}
