/**
 * Unit Tests for AsterSigner
 *
 * Tests the Aster signing strategy implementation in isolation.
 */

import { AsterSigner } from '@/core/credential-manager/signers/AsterSigner'
import { Platform } from '@/types/credential'

describe('AsterSigner Unit Tests', () => {
  let signer: AsterSigner

  beforeEach(() => {
    signer = new AsterSigner()
  })

  describe('Platform Properties', () => {
    test('should have correct platform identifier', () => {
      expect(signer.platform).toBe(Platform.ASTER)
    })

    test('should have correct algorithm identifier', () => {
      expect(signer.algorithm).toBe('eip191')
    })
  })

  describe('Public Key Derivation', () => {
    test('should derive public key from private key', async () => {
      const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'

      const publicKey = await signer.derivePublicKey(privateKey)

      expect(publicKey).toBeDefined()
      expect(typeof publicKey).toBe('string')
      expect(publicKey.startsWith('0x')).toBe(true)
      expect(publicKey.length).toBe(132) // 0x + 64 bytes in hex
    })

    test('should return consistent public key for same private key', async () => {
      const privateKey = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'

      const publicKey1 = await signer.derivePublicKey(privateKey)
      const publicKey2 = await signer.derivePublicKey(privateKey)

      expect(publicKey1).toBe(publicKey2)
    })

    test('should handle private key without 0x prefix', async () => {
      const privateKeyWithoutPrefix = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      const privateKeyWithPrefix = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'

      const publicKey1 = await signer.derivePublicKey(privateKeyWithoutPrefix)
      const publicKey2 = await signer.derivePublicKey(privateKeyWithPrefix)

      expect(publicKey1).toBe(publicKey2)
    })

    test('should throw error for invalid private key format', async () => {
      const invalidKeys = [
        '', // Empty
        'invalid-hex', // Non-hex
        '0x123', // Too short
        '0x' + 'f'.repeat(63), // Wrong length
        '0x' + 'g'.repeat(64) // Invalid hex characters
      ]

      for (const invalidKey of invalidKeys) {
        await expect(signer.derivePublicKey(invalidKey))
          .rejects.toThrow()
      }
    })
  })

  describe('Signing Operations', () => {
    test('should sign message with valid credentials', async () => {
      const message = new TextEncoder().encode('test message')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials)

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.startsWith('0x')).toBe(true)
      expect(signature.length).toBe(132) // 0x + 65 bytes in hex (r + s + v)
    })

    test('should produce deterministic signatures for same message', async () => {
      const message = new TextEncoder().encode('deterministic test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature1 = await signer.sign(message, credentials)
      const signature2 = await signer.sign(message, credentials)

      expect(signature1).toBe(signature2)
    })

    test('should produce different signatures for different messages', async () => {
      const message1 = new TextEncoder().encode('message one')
      const message2 = new TextEncoder().encode('message two')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature1 = await signer.sign(message1, credentials)
      const signature2 = await signer.sign(message2, credentials)

      expect(signature1).not.toBe(signature2)
    })

    test('should produce different signatures for different private keys', async () => {
      const message = new TextEncoder().encode('same message')
      const credentials1 = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }
      const credentials2 = {
        type: 'secp256k1' as const,
        privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature1 = await signer.sign(message, credentials1)
      const signature2 = await signer.sign(message, credentials2)

      expect(signature1).not.toBe(signature2)
    })

    test('should handle empty messages', async () => {
      const emptyMessage = new Uint8Array(0)
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(emptyMessage, credentials)

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.startsWith('0x')).toBe(true)
    })

    test('should handle large messages', async () => {
      const largeMessage = new Uint8Array(1024 * 1024) // 1MB
      largeMessage.fill(42)

      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(largeMessage, credentials)

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.startsWith('0x')).toBe(true)
    })

    test('should handle private key without 0x prefix', async () => {
      const message = new TextEncoder().encode('test message')
      const credentialsWithoutPrefix = {
        type: 'secp256k1' as const,
        privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }
      const credentialsWithPrefix = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature1 = await signer.sign(message, credentialsWithoutPrefix)
      const signature2 = await signer.sign(message, credentialsWithPrefix)

      expect(signature1).toBe(signature2)
    })

    test('should throw error for invalid credentials', async () => {
      const message = new TextEncoder().encode('test message')

      const invalidCredentials = [
        {
          type: 'secp256k1' as const,
          privateKey: '' // Empty key
        },
        {
          type: 'secp256k1' as const,
          privateKey: 'invalid-hex'
        },
        {
          type: 'secp256k1' as const,
          privateKey: '0x' + 'f'.repeat(63) // Wrong length
        },
        {
          type: 'wrong-type' as any,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        }
      ]

      for (const invalidCred of invalidCredentials) {
        await expect(signer.sign(message, invalidCred))
          .rejects.toThrow()
      }
    })
  })

  describe('Verification Operations', () => {
    test('should verify valid signature', async () => {
      const message = new TextEncoder().encode('verification test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials)
      const publicKey = await signer.derivePublicKey(credentials.privateKey)

      const isValid = await signer.verify(message, signature, publicKey)

      expect(isValid).toBe(true)
    })

    test('should reject invalid signature', async () => {
      const message = new TextEncoder().encode('verification test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials)
      const publicKey = await signer.derivePublicKey(credentials.privateKey)

      // Modify signature to make it invalid
      const invalidSignature = signature.slice(0, -2) + '00'

      const isValid = await signer.verify(message, invalidSignature, publicKey)

      expect(isValid).toBe(false)
    })

    test('should reject signature with wrong public key', async () => {
      const message = new TextEncoder().encode('verification test')
      const credentials1 = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }
      const credentials2 = {
        type: 'secp256k1' as const,
        privateKey: '0x2234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials1)
      const wrongPublicKey = await signer.derivePublicKey(credentials2.privateKey)

      const isValid = await signer.verify(message, signature, wrongPublicKey)

      expect(isValid).toBe(false)
    })

    test('should reject signature with wrong message', async () => {
      const message1 = new TextEncoder().encode('original message')
      const message2 = new TextEncoder().encode('different message')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message1, credentials)
      const publicKey = await signer.derivePublicKey(credentials.privateKey)

      const isValid = await signer.verify(message2, signature, publicKey)

      expect(isValid).toBe(false)
    })

    test('should handle malformed signature gracefully', async () => {
      const message = new TextEncoder().encode('test message')
      const publicKey = await signer.derivePublicKey('0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef')

      const malformedSignatures = [
        '', // Empty
        'invalid', // Not hex
        '0x12345', // Too short
        '0x' + 'g'.repeat(130) // Invalid hex characters
      ]

      for (const malformedSig of malformedSignatures) {
        const isValid = await signer.verify(message, malformedSig, publicKey)
        expect(isValid).toBe(false)
      }
    })

    test('should handle malformed public key gracefully', async () => {
      const message = new TextEncoder().encode('test message')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials)

      const malformedPublicKeys = [
        '', // Empty
        'invalid', // Not hex
        '0x12345', // Too short
        '0x' + 'g'.repeat(130) // Invalid hex characters
      ]

      for (const malformedPubKey of malformedPublicKeys) {
        const isValid = await signer.verify(message, signature, malformedPubKey)
        expect(isValid).toBe(false)
      }
    })
  })

  describe('EIP-191 Personal Message Signing', () => {
    test('should implement EIP-191 personal message format', async () => {
      const message = new TextEncoder().encode('Hello Ethereum!')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials)

      // EIP-191 signature should be recoverable
      expect(signature).toBeDefined()
      expect(signature.length).toBe(132) // 0x + 65 bytes

      // The last byte should be the recovery parameter (27 or 28 for legacy, 0 or 1 for new)
      const lastByte = signature.slice(-2)
      const recoveryParam = parseInt(lastByte, 16)
      expect([0, 1, 27, 28]).toContain(recoveryParam)
    })

    test('should prefix message with EIP-191 header', async () => {
      const originalMessage = 'test message'
      const messageBytes = new TextEncoder().encode(originalMessage)
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      // Sign the same message content but constructed differently
      const signature1 = await signer.sign(messageBytes, credentials)

      // Should be consistent
      const signature2 = await signer.sign(messageBytes, credentials)
      expect(signature1).toBe(signature2)
    })

    test('should handle unicode messages properly', async () => {
      const unicodeMessage = new TextEncoder().encode('Hello 世界! 🌍')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(unicodeMessage, credentials)

      expect(signature).toBeDefined()
      expect(signature.length).toBe(132)
    })
  })

  describe('Performance Requirements', () => {
    test('should sign within performance requirements', async () => {
      const message = new TextEncoder().encode('performance test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const startTime = performance.now()
      await signer.sign(message, credentials)
      const duration = performance.now() - startTime

      expect(duration).toBeLessThan(50) // Should meet performance requirement
    })

    test('should verify within performance requirements', async () => {
      const message = new TextEncoder().encode('verification performance test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature = await signer.sign(message, credentials)
      const publicKey = await signer.derivePublicKey(credentials.privateKey)

      const startTime = performance.now()
      await signer.verify(message, signature, publicKey)
      const duration = performance.now() - startTime

      expect(duration).toBeLessThan(50)
    })

    test('should handle concurrent operations efficiently', async () => {
      const message = new TextEncoder().encode('concurrent test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const startTime = performance.now()

      const promises = Array.from({ length: 10 }, () => signer.sign(message, credentials))
      const signatures = await Promise.all(promises)

      const duration = performance.now() - startTime

      expect(signatures).toHaveLength(10)
      expect(signatures.every(sig => sig === signatures[0])).toBe(true) // Deterministic
      expect(duration).toBeLessThan(200) // 10 concurrent operations
    })
  })

  describe('Credential Validation', () => {
    test('should accept valid secp256k1 credentials', () => {
      const validCredentials = [
        {
          type: 'secp256k1' as const,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        },
        {
          type: 'secp256k1' as const,
          privateKey: '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        },
        {
          type: 'secp256k1' as const,
          privateKey: '0xABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789'
        }
      ]

      validCredentials.forEach(creds => {
        expect(() => signer.validateCredentials(creds)).not.toThrow()
      })
    })

    test('should reject invalid credential types', () => {
      const invalidCredentials = [
        {
          type: 'ed25519' as any,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        },
        {
          type: 'hmac' as any,
          apiKey: 'test',
          secretKey: 'test'
        },
        {
          type: 'rsa' as any,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        }
      ]

      invalidCredentials.forEach(creds => {
        expect(() => signer.validateCredentials(creds)).toThrow()
      })
    })

    test('should reject malformed private keys', () => {
      const invalidPrivateKeys = [
        '', // Empty
        'invalid-hex',
        '0x123', // Too short
        '0x' + 'f'.repeat(63), // 31 bytes
        '0x' + 'f'.repeat(65), // 33 bytes
        '0x' + 'g'.repeat(64) // Invalid hex
      ]

      invalidPrivateKeys.forEach(privateKey => {
        const credentials = {
          type: 'secp256k1' as const,
          privateKey
        }
        expect(() => signer.validateCredentials(credentials)).toThrow()
      })
    })
  })

  describe('Edge Cases', () => {
    test('should handle null and undefined inputs gracefully', async () => {
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      await expect(signer.sign(null as any, credentials)).rejects.toThrow()
      await expect(signer.sign(undefined as any, credentials)).rejects.toThrow()

      const message = new TextEncoder().encode('test')
      await expect(signer.sign(message, null as any)).rejects.toThrow()
      await expect(signer.sign(message, undefined as any)).rejects.toThrow()
    })

    test('should handle very large messages efficiently', async () => {
      const megaMessage = new Uint8Array(10 * 1024 * 1024) // 10MB
      megaMessage.fill(123)

      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const startTime = performance.now()
      const signature = await signer.sign(megaMessage, credentials)
      const duration = performance.now() - startTime

      expect(signature).toBeDefined()
      expect(duration).toBeLessThan(2000) // ECDSA with large messages might be slower
    })

    test('should maintain consistency across multiple instances', async () => {
      const signer1 = new AsterSigner()
      const signer2 = new AsterSigner()

      const message = new TextEncoder().encode('consistency test')
      const credentials = {
        type: 'secp256k1' as const,
        privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
      }

      const signature1 = await signer1.sign(message, credentials)
      const signature2 = await signer2.sign(message, credentials)

      expect(signature1).toBe(signature2)
    })

    test('should handle boundary values for private keys', async () => {
      const message = new TextEncoder().encode('boundary test')

      // Test with minimum valid private key (1)
      const minCredentials = {
        type: 'secp256k1' as const,
        privateKey: '0x0000000000000000000000000000000000000000000000000000000000000001'
      }

      const signature1 = await signer.sign(message, minCredentials)
      expect(signature1).toBeDefined()

      // Test with maximum valid private key (close to secp256k1 order)
      const maxCredentials = {
        type: 'secp256k1' as const,
        privateKey: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140'
      }

      const signature2 = await signer.sign(message, maxCredentials)
      expect(signature2).toBeDefined()
    })
  })
})