/**
 * Aster Network Signer Implementation
 *
 * Implements ECDSA secp256k1 and EIP-191 signing for Aster Network transactions
 * according to Ethereum standards and Aster Network specifications.
 */

import sha3 from 'js-sha3'

import { Platform, Credentials, AsterCredentials, CredentialManagerError, ErrorType } from '@/types/credential'

// Dynamic import for secp256k1 to handle ESM/CJS compatibility
let secp256k1: any

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

export interface AsterSignRequest {
  accountId: string
  transaction: AsterTransaction
  options?: AsterSignOptions
}

export interface AsterMessageSignRequest {
  accountId: string
  message: string | Uint8Array
  options?: AsterSignOptions
}

export interface AsterSignOptions {
  timeout?: number
  chainId?: number
  gasLimit?: string
  gasPrice?: string
  nonce?: number
}

export interface AsterTransaction {
  to: string
  value?: string
  data?: string
  gasLimit?: string
  gasPrice?: string
  nonce?: number
  chainId?: number
}

export interface AsterSignResponse {
  success: boolean
  signature: string
  algorithm: 'ecdsa-secp256k1' | 'eip-191'
  address: string
  chainId?: number
  txHash?: string
  timestamp: Date
  error?: string
}

export interface AsterVerifyRequest {
  message: string | Uint8Array
  signature: string
  address: string
  algorithm?: 'ecdsa-secp256k1' | 'eip-191'
}

export interface AsterVerifyResponse {
  valid: boolean
  algorithm: 'ecdsa-secp256k1' | 'eip-191'
  address: string
  timestamp: Date
}

// ============================================================================
// Aster Signer Implementation
// ============================================================================

export class AsterSigner {
  readonly platform = Platform.ASTER

  private accounts = new Map<string, { credentials: AsterCredentials; address: string }>()
  private signMetrics = {
    totalSigns: 0,
    successfulSigns: 0,
    failedSigns: 0,
    totalTime: 0,
  }

  constructor() {
    // Initialize with empty accounts map
    this.initializeSecp256k1()
  }

  /**
   * Initialize secp256k1 library with dynamic import
   */
  private async initializeSecp256k1(): Promise<void> {
    try {
      // Try to import noble-secp256k1 which is preferred for TypeScript
      const { secp256k1: nobleSecp } = await import('@noble/secp256k1')
      secp256k1 = nobleSecp
    } catch (error) {
      // Fallback to native secp256k1 if available
      try {
        secp256k1 = await import('secp256k1')
      } catch (fallbackError) {
        throw new CredentialManagerError(
          'No secp256k1 library available. Please install @noble/secp256k1 or secp256k1',
          ErrorType.SIGNATURE_ERROR,
        )
      }
    }
  }

  /**
   * Register an account with its credentials
   */
  async registerAccount(accountId: string, credentials: AsterCredentials): Promise<void> {
    if (!this.validateCredentials(credentials)) {
      throw new CredentialManagerError(`Invalid Aster credentials for account ${accountId}`, ErrorType.VALIDATION_ERROR)
    }

    const address = await this.deriveAddress(credentials.privateKey)
    this.accounts.set(accountId, { credentials, address })
  }

  /**
   * Remove an account
   */
  removeAccount(accountId: string): boolean {
    return this.accounts.delete(accountId)
  }

  /**
   * Sign Aster Network transaction
   */
  async signTransaction(request: AsterSignRequest): Promise<AsterSignResponse> {
    const startTime = Date.now()
    this.signMetrics.totalSigns++

    try {
      const account = this.accounts.get(request.accountId)
      if (!account) {
        throw new CredentialManagerError(`Account ${request.accountId} not found`, ErrorType.SIGNATURE_ERROR)
      }

      const { credentials, address } = account
      const transaction = this.prepareTransaction(request.transaction, request.options)

      // Serialize transaction for signing
      const serializedTx = this.serializeTransaction(transaction)
      const txHash = this.keccak256Hash(serializedTx)

      // Sign with ECDSA secp256k1
      const signature = await this.signHash(txHash, credentials.privateKey)

      const duration = Date.now() - startTime
      this.signMetrics.successfulSigns++
      this.signMetrics.totalTime += duration

      return {
        success: true,
        signature,
        algorithm: 'ecdsa-secp256k1',
        address,
        chainId: transaction.chainId,
        txHash: `0x${txHash}`,
        timestamp: new Date(),
      }
    } catch (error) {
      const duration = Date.now() - startTime
      this.signMetrics.failedSigns++
      this.signMetrics.totalTime += duration

      return {
        success: false,
        signature: '',
        algorithm: 'ecdsa-secp256k1',
        address: '',
        timestamp: new Date(),
        error: error instanceof Error ? error.message : 'Unknown signing error',
      }
    }
  }

  /**
   * Sign arbitrary message using EIP-191
   */
  async signMessage(request: AsterMessageSignRequest): Promise<AsterSignResponse> {
    const startTime = Date.now()
    this.signMetrics.totalSigns++

    try {
      const account = this.accounts.get(request.accountId)
      if (!account) {
        throw new CredentialManagerError(`Account ${request.accountId} not found`, ErrorType.SIGNATURE_ERROR)
      }

      const { credentials, address } = account

      // Convert message to string if it's binary
      const message = typeof request.message === 'string' ? request.message : new TextDecoder().decode(request.message)

      // Create EIP-191 formatted message
      const eip191Message = this.formatEip191Message(message)
      const messageHash = this.keccak256Hash(eip191Message)

      // Sign with ECDSA secp256k1
      const signature = await this.signHash(messageHash, credentials.privateKey)

      const duration = Date.now() - startTime
      this.signMetrics.successfulSigns++
      this.signMetrics.totalTime += duration

      return {
        success: true,
        signature,
        algorithm: 'eip-191',
        address,
        timestamp: new Date(),
      }
    } catch (error) {
      const duration = Date.now() - startTime
      this.signMetrics.failedSigns++
      this.signMetrics.totalTime += duration

      return {
        success: false,
        signature: '',
        algorithm: 'eip-191',
        address: '',
        timestamp: new Date(),
        error: error instanceof Error ? error.message : 'Unknown signing error',
      }
    }
  }

  /**
   * Verify ECDSA signature
   */
  async verifySignature(request: AsterVerifyRequest): Promise<AsterVerifyResponse> {
    try {
      const message = typeof request.message === 'string' ? request.message : new TextDecoder().decode(request.message)

      let messageHash: string

      if (request.algorithm === 'eip-191' || !request.algorithm) {
        // Default to EIP-191 for message verification
        const eip191Message = this.formatEip191Message(message)
        messageHash = this.keccak256Hash(eip191Message)
      } else {
        // Direct message hash for transaction verification
        messageHash = this.keccak256Hash(message)
      }

      const valid = await this.verifyEcdsaSignature(messageHash, request.signature, request.address)

      return {
        valid,
        algorithm: request.algorithm || 'eip-191',
        address: request.address,
        timestamp: new Date(),
      }
    } catch (error) {
      return {
        valid: false,
        algorithm: request.algorithm || 'eip-191',
        address: request.address,
        timestamp: new Date(),
      }
    }
  }

  /**
   * Get Ethereum address for account
   */
  async getAddress(accountId: string): Promise<string> {
    const account = this.accounts.get(accountId)
    if (!account) {
      throw new CredentialManagerError(`Account ${accountId} not found`, ErrorType.SIGNATURE_ERROR)
    }

    return account.address
  }

  /**
   * Sign multiple transactions in batch
   */
  async signBatch(requests: AsterSignRequest[]): Promise<AsterSignResponse[]> {
    // Process requests in parallel for better performance
    const promises = requests.map(request => this.signTransaction(request))
    return Promise.all(promises)
  }

  /**
   * Get signing metrics for monitoring
   */
  getMetrics() {
    const avgTime = this.signMetrics.totalSigns > 0 ? this.signMetrics.totalTime / this.signMetrics.totalSigns : 0

    return {
      totalSigns: this.signMetrics.totalSigns,
      successfulSigns: this.signMetrics.successfulSigns,
      failedSigns: this.signMetrics.failedSigns,
      successRate: this.signMetrics.totalSigns > 0 ? this.signMetrics.successfulSigns / this.signMetrics.totalSigns : 0,
      averageSignTime: avgTime,
    }
  }

  /**
   * Reset metrics
   */
  resetMetrics(): void {
    this.signMetrics = {
      totalSigns: 0,
      successfulSigns: 0,
      failedSigns: 0,
      totalTime: 0,
    }
  }

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

  /**
   * Validate Aster credentials format
   */
  private validateCredentials(credentials: AsterCredentials): boolean {
    return (
      credentials.type === 'aster' &&
      typeof credentials.privateKey === 'string' &&
      /^0x[0-9a-fA-F]{64}$/.test(credentials.privateKey)
    )
  }

  /**
   * Derive Ethereum address from private key
   */
  private async deriveAddress(privateKey: string): Promise<string> {
    try {
      // Remove 0x prefix if present
      const cleanPrivateKey = privateKey.replace(/^0x/, '')
      const privateKeyBytes = Buffer.from(cleanPrivateKey, 'hex')

      // Get public key from private key
      const publicKey = await secp256k1.getPublicKey(privateKeyBytes, false) // Uncompressed
      const publicKeyBytes = publicKey.slice(1) // Remove 0x04 prefix

      // Hash public key with Keccak256 and take last 20 bytes
      const addressBytes = this.keccak256Hash(publicKeyBytes).slice(-40)
      return `0x${addressBytes}`
    } catch (error) {
      throw new CredentialManagerError(
        `Failed to derive address from private key: ${error.message}`,
        ErrorType.VALIDATION_ERROR,
      )
    }
  }

  /**
   * Prepare transaction with default values
   */
  private prepareTransaction(transaction: AsterTransaction, options?: AsterSignOptions): AsterTransaction {
    return {
      to: transaction.to,
      value: transaction.value || '0',
      data: transaction.data || '0x',
      gasLimit: options?.gasLimit || transaction.gasLimit || '21000',
      gasPrice: options?.gasPrice || transaction.gasPrice || '20000000000', // 20 Gwei
      nonce: options?.nonce ?? transaction.nonce ?? 0,
      chainId: options?.chainId || transaction.chainId || ASTER_CONSTANTS.MAINNET_CHAIN_ID,
    }
  }

  /**
   * Serialize transaction for signing (simplified RLP encoding)
   */
  private serializeTransaction(tx: AsterTransaction): string {
    // Simplified serialization - in production, use proper RLP encoding
    return JSON.stringify({
      to: tx.to,
      value: tx.value,
      data: tx.data,
      gasLimit: tx.gasLimit,
      gasPrice: tx.gasPrice,
      nonce: tx.nonce,
      chainId: tx.chainId,
    })
  }

  /**
   * Format message according to EIP-191
   */
  private formatEip191Message(message: string): string {
    return `\x19Ethereum Signed Message:\n${message.length}${message}`
  }

  /**
   * Compute Keccak256 hash
   */
  private keccak256Hash(data: string | Uint8Array): string {
    if (typeof data === 'string') {
      return sha3.keccak256(data)
    }
    return sha3.keccak256(data)
  }

  /**
   * Sign hash with ECDSA secp256k1
   */
  private async signHash(hash: string, privateKey: string): Promise<string> {
    try {
      const cleanPrivateKey = privateKey.replace(/^0x/, '')
      const privateKeyBytes = Buffer.from(cleanPrivateKey, 'hex')
      const hashBytes = Buffer.from(hash, 'hex')

      const signature = await secp256k1.sign(hashBytes, privateKeyBytes)

      // Format as 0x-prefixed hex string with recovery id
      const r = signature.r.toString(16).padStart(64, '0')
      const s = signature.s.toString(16).padStart(64, '0')
      const v = (signature.recovery + 27).toString(16).padStart(2, '0')

      return `0x${r}${s}${v}`
    } catch (error) {
      throw new CredentialManagerError(`Failed to sign hash: ${error.message}`, ErrorType.SIGNATURE_ERROR)
    }
  }

  /**
   * Verify ECDSA signature
   */
  private async verifyEcdsaSignature(hash: string, signature: string, expectedAddress: string): Promise<boolean> {
    try {
      // Extract r, s, v from signature
      const sig = signature.replace(/^0x/, '')
      if (sig.length !== 130) {
        return false
      }

      const r = sig.slice(0, 64)
      const s = sig.slice(64, 128)
      const v = parseInt(sig.slice(128, 130), 16)

      // Recover public key and derive address
      const recoveryId = v - 27
      const hashBytes = Buffer.from(hash, 'hex')
      const signatureBytes = {
        r: Buffer.from(r, 'hex'),
        s: Buffer.from(s, 'hex'),
        recovery: recoveryId,
      }

      const publicKey = await secp256k1.recover(hashBytes, signatureBytes, recoveryId, false)
      const publicKeyBytes = publicKey.slice(1) // Remove 0x04 prefix

      const addressBytes = this.keccak256Hash(publicKeyBytes).slice(-40)
      const recoveredAddress = `0x${addressBytes}`

      return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase()
    } catch (error) {
      return false
    }
  }
}

// ============================================================================
// Aster Network Constants
// ============================================================================

export const ASTER_CONSTANTS = {
  /**
   * Aster Network mainnet chain ID
   */
  MAINNET_CHAIN_ID: 592,

  /**
   * Aster Network testnet chain ID
   */
  TESTNET_CHAIN_ID: 81,

  /**
   * Default gas limit for simple transfers
   */
  DEFAULT_GAS_LIMIT: '21000',

  /**
   * Default gas price (20 Gwei)
   */
  DEFAULT_GAS_PRICE: '20000000000',

  /**
   * Maximum gas limit
   */
  MAX_GAS_LIMIT: '10000000',

  /**
   * EIP-191 message prefix
   */
  EIP191_PREFIX: '\x19Ethereum Signed Message:\n',

  /**
   * Private key length in bytes
   */
  PRIVATE_KEY_LENGTH: 32,

  /**
   * Address length in bytes
   */
  ADDRESS_LENGTH: 20,

  /**
   * Signature length in bytes
   */
  SIGNATURE_LENGTH: 65,

  /**
   * Default timeout for operations
   */
  DEFAULT_TIMEOUT: 30000,
} as const

// ============================================================================
// Utility Functions
// ============================================================================

/**
 * Validate Ethereum address format
 */
export function validateAddressFormat(address: string): boolean {
  return /^0x[0-9a-fA-F]{40}$/.test(address)
}

/**
 * Validate private key format
 */
export function validatePrivateKeyFormat(privateKey: string): boolean {
  return /^0x[0-9a-fA-F]{64}$/.test(privateKey)
}

/**
 * Convert Wei to Ether
 */
export function weiToEther(wei: string): string {
  const weiValue = BigInt(wei)
  const etherValue = weiValue / BigInt('1000000000000000000')
  return etherValue.toString()
}

/**
 * Convert Ether to Wei
 */
export function etherToWei(ether: string): string {
  const etherValue = parseFloat(ether)
  const weiValue = BigInt(Math.floor(etherValue * 1e18))
  return weiValue.toString()
}

/**
 * Estimate gas for transaction
 */
export function estimateGas(transaction: AsterTransaction): string {
  // Basic gas estimation - in production, use proper gas estimation
  if (transaction.data && transaction.data !== '0x') {
    // Contract interaction
    return '100000'
  } else {
    // Simple transfer
    return '21000'
  }
}

// ============================================================================
// Export
// ============================================================================

export default AsterSigner
