/**
 * Unit Tests for BinanceSigner
 *
 * Tests the Binance signing strategy implementation in isolation.
 */

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

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

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

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

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

  describe('HMAC Signing Operations', () => {
    test('should sign message with valid credentials', async () => {
      const message = new TextEncoder().encode('test message')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.length).toBe(64) // SHA256 hash is 32 bytes = 64 hex chars
    })

    test('should produce deterministic signatures', async () => {
      const message = new TextEncoder().encode('deterministic test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

      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: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

      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 secret keys', async () => {
      const message = new TextEncoder().encode('same message')
      const credentials1 = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'secret-key-one'
      }
      const credentials2 = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'secret-key-two'
      }

      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: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.length).toBe(64)
    })

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

      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.length).toBe(64)
    })

    test('should handle special characters in keys', async () => {
      const message = new TextEncoder().encode('test message')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'api-key-with-special-chars!@#$%^&*()',
        secretKey: 'secret-key-with-unicode-世界-and-emojis-🚀'
      }

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

      expect(signature).toBeDefined()
      expect(typeof signature).toBe('string')
      expect(signature.length).toBe(64)
    })

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

      const invalidCredentials = [
        {
          type: 'hmac' as const,
          apiKey: '', // Empty API key
          secretKey: 'test-secret-key'
        },
        {
          type: 'hmac' as const,
          apiKey: 'test-api-key',
          secretKey: '' // Empty secret key
        },
        {
          type: 'hmac' as const,
          apiKey: 'test-api-key'
          // Missing secret key
        },
        {
          type: 'wrong-type' as any,
          apiKey: 'test-api-key',
          secretKey: 'test-secret-key'
        }
      ]

      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: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      // For HMAC, verification is just re-computing and comparing
      const isValid = await signer.verify(message, signature, credentials.secretKey)

      expect(isValid).toBe(true)
    })

    test('should reject invalid signature', async () => {
      const message = new TextEncoder().encode('verification test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

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

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

      expect(isValid).toBe(false)
    })

    test('should reject signature with wrong secret key', async () => {
      const message = new TextEncoder().encode('verification test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'correct-secret-key'
      }

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

      const isValid = await signer.verify(message, signature, 'wrong-secret-key')

      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: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

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

      expect(isValid).toBe(false)
    })

    test('should handle malformed signature gracefully', async () => {
      const message = new TextEncoder().encode('test message')
      const secretKey = 'test-secret-key'

      const malformedSignatures = [
        '', // Empty
        'invalid', // Not hex
        '12345', // Too short
        'g'.repeat(64) // Invalid hex characters
      ]

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

  describe('Binance-Specific Query String Signing', () => {
    test('should sign query parameters correctly', async () => {
      const queryParams = 'symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000&timestamp=1499827319559'
      const message = new TextEncoder().encode(queryParams)
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A',
        secretKey: 'NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j'
      }

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

      // This should match Binance's expected signature format
      expect(signature).toBeDefined()
      expect(signature.length).toBe(64)
      expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true)
    })

    test('should handle URL encoding properly', async () => {
      const queryWithEncoding = 'symbol=BTC%2FUSDT&side=BUY&quantity=1.0&timestamp=1635724800000'
      const message = new TextEncoder().encode(queryWithEncoding)
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

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

    test('should handle timestamp parameter correctly', async () => {
      const timestamp = Date.now()
      const queryWithTimestamp = `symbol=BTCUSDT&side=BUY&type=MARKET&quantity=0.001&timestamp=${timestamp}`
      const message = new TextEncoder().encode(queryWithTimestamp)
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(signature.length).toBe(64)

      // Should be reproducible with same timestamp
      const signature2 = await signer.sign(message, credentials)
      expect(signature).toBe(signature2)
    })
  })

  describe('Performance Requirements', () => {
    test('should sign within performance requirements', async () => {
      const message = new TextEncoder().encode('performance test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(duration).toBeLessThan(25) // HMAC should be very fast
    })

    test('should verify within performance requirements', async () => {
      const message = new TextEncoder().encode('verification performance test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

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

      expect(duration).toBeLessThan(25) // HMAC verification should be very fast
    })

    test('should handle concurrent operations efficiently', async () => {
      const message = new TextEncoder().encode('concurrent test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

      const startTime = performance.now()

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

      const duration = performance.now() - startTime

      expect(signatures).toHaveLength(20)
      expect(signatures.every(sig => sig === signatures[0])).toBe(true) // Deterministic
      expect(duration).toBeLessThan(100) // 20 concurrent operations should be very fast
    })

    test('should handle high-frequency trading scenarios', async () => {
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

      // Simulate rapid order signing
      const startTime = performance.now()
      const signatures: string[] = []

      for (let i = 0; i < 100; i++) {
        const timestamp = Date.now() + i
        const queryString = `symbol=BTCUSDT&side=BUY&type=LIMIT&quantity=0.001&price=50000&timestamp=${timestamp}`
        const message = new TextEncoder().encode(queryString)

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

      const duration = performance.now() - startTime

      expect(signatures).toHaveLength(100)
      expect(new Set(signatures).size).toBe(100) // All should be unique due to timestamps
      expect(duration).toBeLessThan(200) // 100 signatures in 200ms for HFT
    })
  })

  describe('Credential Validation', () => {
    test('should accept valid HMAC credentials', () => {
      const validCredentials = [
        {
          type: 'hmac' as const,
          apiKey: 'test-api-key',
          secretKey: 'test-secret-key'
        },
        {
          type: 'hmac' as const,
          apiKey: 'API_KEY_WITH_UNDERSCORES',
          secretKey: 'SECRET_KEY_WITH_UNDERSCORES'
        },
        {
          type: 'hmac' as const,
          apiKey: 'api-key-with-dashes',
          secretKey: 'secret-key-with-dashes'
        },
        {
          type: 'hmac' as const,
          apiKey: '1234567890',
          secretKey: 'abcdefghij'
        }
      ]

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

    test('should reject invalid credential types', () => {
      const invalidCredentials = [
        {
          type: 'ed25519' as any,
          apiKey: 'test-api-key',
          secretKey: 'test-secret-key'
        },
        {
          type: 'secp256k1' as any,
          privateKey: '0x1234567890abcdef'
        },
        {
          type: 'rsa' as any,
          apiKey: 'test-api-key',
          secretKey: 'test-secret-key'
        }
      ]

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

    test('should reject credentials with missing fields', () => {
      const incompleteCredentials = [
        {
          type: 'hmac' as const,
          apiKey: 'test-api-key'
          // Missing secretKey
        },
        {
          type: 'hmac' as const,
          secretKey: 'test-secret-key'
          // Missing apiKey
        },
        {
          type: 'hmac' as const,
          apiKey: '',
          secretKey: 'test-secret-key'
        },
        {
          type: 'hmac' as const,
          apiKey: 'test-api-key',
          secretKey: ''
        }
      ]

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

  describe('Edge Cases', () => {
    test('should handle null and undefined inputs gracefully', async () => {
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

      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: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(duration).toBeLessThan(500) // HMAC should handle large messages efficiently
    })

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

      const message = new TextEncoder().encode('consistency test')
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature1).toBe(signature2)
    })

    test('should handle binary data in messages', async () => {
      const binaryMessage = new Uint8Array([0, 1, 2, 3, 255, 254, 253, 252])
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(signature.length).toBe(64)
      expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true)
    })

    test('should handle keys with various encodings', async () => {
      const message = new TextEncoder().encode('encoding test')

      const credentials = [
        {
          type: 'hmac' as const,
          apiKey: 'base64-style-key==',
          secretKey: 'base64-style-secret=='
        },
        {
          type: 'hmac' as const,
          apiKey: 'hex-style-key-123abc',
          secretKey: 'hex-style-secret-456def'
        },
        {
          type: 'hmac' as const,
          apiKey: 'unicode-key-世界',
          secretKey: 'unicode-secret-🌍'
        }
      ]

      for (const creds of credentials) {
        const signature = await signer.sign(message, creds)
        expect(signature).toBeDefined()
        expect(signature.length).toBe(64)
      }
    })
  })

  describe('Binance API Compatibility', () => {
    test('should produce signatures compatible with Binance API format', async () => {
      // Test with realistic Binance API parameters
      const params = {
        symbol: 'BTCUSDT',
        side: 'BUY',
        type: 'LIMIT',
        timeInForce: 'GTC',
        quantity: '0.001',
        price: '50000.00',
        timestamp: '1635724800000',
        recvWindow: '5000'
      }

      const queryString = Object.entries(params)
        .sort(([a], [b]) => a.localeCompare(b)) // Binance requires sorted params
        .map(([key, value]) => `${key}=${value}`)
        .join('&')

      const message = new TextEncoder().encode(queryString)
      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-binance-api-key',
        secretKey: 'test-binance-secret-key'
      }

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

      expect(signature).toBeDefined()
      expect(signature.length).toBe(64)
      expect(/^[0-9a-f]{64}$/i.test(signature)).toBe(true)

      // Signature should be verifiable
      const isValid = await signer.verify(message, signature, credentials.secretKey)
      expect(isValid).toBe(true)
    })

    test('should handle order parameter variations', async () => {
      const orderTypes = [
        'symbol=ETHUSDT&side=BUY&type=MARKET&quantity=0.1',
        'symbol=ADAUSDT&side=SELL&type=LIMIT&quantity=100&price=1.5',
        'symbol=BNBUSDT&side=BUY&type=STOP_LOSS_LIMIT&quantity=10&price=400&stopPrice=390&timeInForce=GTC'
      ]

      const credentials = {
        type: 'hmac' as const,
        apiKey: 'test-api-key',
        secretKey: 'test-secret-key'
      }

      for (const orderParams of orderTypes) {
        const fullParams = `${orderParams}&timestamp=${Date.now()}`
        const message = new TextEncoder().encode(fullParams)

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

        expect(signature).toBeDefined()
        expect(signature.length).toBe(64)

        // Should be verifiable
        const isValid = await signer.verify(message, signature, credentials.secretKey)
        expect(isValid).toBe(true)
      }
    })
  })
})