/**
 * Unit Tests for UnifiedSigner
 *
 * Tests the UnifiedSigner service implementation in isolation.
 */

import { UnifiedSigner, BatchSignRequest } from '@/core/credential-manager/UnifiedSigner'
import { PacificaSigner } from '@/core/credential-manager/signers/PacificaSigner'
import { AsterSigner } from '@/core/credential-manager/signers/AsterSigner'
import { BinanceSigner } from '@/core/credential-manager/signers/BinanceSigner'
import { Platform } from '@/types/credential'

describe('UnifiedSigner Unit Tests', () => {
  let unifiedSigner: UnifiedSigner
  let mockGetAccount: jest.Mock

  beforeEach(() => {
    unifiedSigner = new UnifiedSigner()
    mockGetAccount = jest.fn()
    unifiedSigner.setAccountGetter(mockGetAccount)
  })

  afterEach(() => {
    jest.clearAllMocks()
  })

  describe('Initialization and Configuration', () => {
    test('should initialize with default strategies', () => {
      const supportedPlatforms = unifiedSigner.getSupportedPlatforms()

      expect(supportedPlatforms).toContain(Platform.PACIFICA)
      expect(supportedPlatforms).toContain(Platform.ASTER)
      expect(supportedPlatforms).toContain(Platform.BINANCE)
      expect(supportedPlatforms).toHaveLength(3)
    })

    test('should allow registering custom strategies', () => {
      const customSigner = new PacificaSigner()
      const customPlatform = 'CUSTOM' as Platform

      // Mock the platform property for testing
      Object.defineProperty(customSigner, 'platform', {
        value: customPlatform,
        writable: false
      })

      unifiedSigner.registerStrategy(customPlatform, customSigner)

      const supportedPlatforms = unifiedSigner.getSupportedPlatforms()
      expect(supportedPlatforms).toContain(customPlatform)
    })

    test('should throw error for mismatched platform in strategy registration', () => {
      const pacificaSigner = new PacificaSigner()

      expect(() => {
        unifiedSigner.registerStrategy(Platform.ASTER, pacificaSigner)
      }).toThrow('Strategy platform mismatch')
    })

    test('should throw error for invalid strategy registration parameters', () => {
      expect(() => {
        unifiedSigner.registerStrategy(null as any, null as any)
      }).toThrow('Platform and strategy are required')
    })

    test('should require account getter to be set', async () => {
      const signerWithoutGetter = new UnifiedSigner()
      const message = new TextEncoder().encode('test')

      await expect(signerWithoutGetter.sign('test-account', message))
        .rejects.toThrow('Account getter function not configured')
    })
  })

  describe('Signing Operations', () => {
    test('should sign with Pacifica account', async () => {
      const pacificaAccount = {
        id: 'pac-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(pacificaAccount)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('pac-account', message)

      expect(result.success).toBe(true)
      expect(result.algorithm).toBe('ed25519')
      expect(result.metadata?.platform).toBe(Platform.PACIFICA)
      expect(mockGetAccount).toHaveBeenCalledWith('pac-account')
    })

    test('should sign with Aster account', async () => {
      const asterAccount = {
        id: 'ast-account',
        platform: Platform.ASTER,
        credentials: {
          type: 'secp256k1' as const,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        }
      }

      mockGetAccount.mockResolvedValue(asterAccount)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('ast-account', message)

      expect(result.success).toBe(true)
      expect(result.algorithm).toBe('eip191')
      expect(result.metadata?.platform).toBe(Platform.ASTER)
    })

    test('should sign with Binance account', async () => {
      const binanceAccount = {
        id: 'bnb-account',
        platform: Platform.BINANCE,
        credentials: {
          type: 'hmac' as const,
          apiKey: 'test-api-key',
          secretKey: 'test-secret-key'
        }
      }

      mockGetAccount.mockResolvedValue(binanceAccount)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('bnb-account', message)

      expect(result.success).toBe(true)
      expect(result.algorithm).toBe('hmac-sha256')
      expect(result.metadata?.platform).toBe(Platform.BINANCE)
    })

    test('should handle account not found error', async () => {
      mockGetAccount.mockResolvedValue(null)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('non-existent', message)

      expect(result.success).toBe(false)
      expect(result.error).toContain('Account not found')
    })

    test('should handle platform detection failure', async () => {
      const accountWithoutPlatform = {
        id: 'invalid-account',
        credentials: {
          type: 'unknown' as any
        }
      }

      mockGetAccount.mockResolvedValue(accountWithoutPlatform)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('invalid-account', message)

      expect(result.success).toBe(false)
      expect(result.error).toContain('Cannot determine platform')
    })

    test('should handle unsupported platform', async () => {
      const accountWithUnsupportedPlatform = {
        id: 'unsupported-account',
        platform: 'UNSUPPORTED' as Platform,
        credentials: {
          type: 'unknown' as any
        }
      }

      mockGetAccount.mockResolvedValue(accountWithUnsupportedPlatform)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('unsupported-account', message)

      expect(result.success).toBe(false)
      expect(result.error).toContain('No strategy available')
    })

    test('should respect force platform option', async () => {
      const asterAccount = {
        id: 'ast-account',
        platform: Platform.ASTER,
        credentials: {
          type: 'secp256k1' as const,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        }
      }

      mockGetAccount.mockResolvedValue(asterAccount)

      const message = new TextEncoder().encode('test message')
      const result = await unifiedSigner.sign('ast-account', message, {
        forcePlatform: Platform.ASTER
      })

      expect(result.success).toBe(true)
      expect(result.metadata?.platform).toBe(Platform.ASTER)
    })
  })

  describe('Verification Operations', () => {
    test('should verify Pacifica signatures', async () => {
      const pacificaAccount = {
        id: 'pac-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(pacificaAccount)

      const message = new TextEncoder().encode('verification test')

      // First sign the message
      const signResult = await unifiedSigner.sign('pac-account', message)
      expect(signResult.success).toBe(true)

      // Then verify it
      const isValid = await unifiedSigner.verify(
        'pac-account',
        message,
        signResult.signature!
      )

      expect(isValid).toBe(true)
    })

    test('should reject invalid signatures during verification', async () => {
      const pacificaAccount = {
        id: 'pac-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(pacificaAccount)

      const message = new TextEncoder().encode('verification test')
      const invalidSignature = 'invalid-signature'

      const isValid = await unifiedSigner.verify('pac-account', message, invalidSignature)

      expect(isValid).toBe(false)
    })

    test('should handle verification errors gracefully', async () => {
      mockGetAccount.mockResolvedValue(null)

      const message = new TextEncoder().encode('test')
      const signature = 'some-signature'

      const isValid = await unifiedSigner.verify('non-existent', message, signature)

      expect(isValid).toBe(false)
    })
  })

  describe('Batch Operations', () => {
    test('should handle batch signing requests', async () => {
      const pacificaAccount = {
        id: 'pac-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      const asterAccount = {
        id: 'ast-account',
        platform: Platform.ASTER,
        credentials: {
          type: 'secp256k1' as const,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        }
      }

      mockGetAccount
        .mockResolvedValueOnce(pacificaAccount)
        .mockResolvedValueOnce(asterAccount)

      const batchRequests: BatchSignRequest[] = [
        {
          accountId: 'pac-account',
          message: new TextEncoder().encode('pacifica message')
        },
        {
          accountId: 'ast-account',
          message: new TextEncoder().encode('aster message')
        }
      ]

      const results = await unifiedSigner.signBatch(batchRequests)

      expect(results).toHaveLength(2)
      expect(results[0].success).toBe(true)
      expect(results[0].accountId).toBe('pac-account')
      expect(results[1].success).toBe(true)
      expect(results[1].accountId).toBe('ast-account')
    })

    test('should handle batch requests with failures', async () => {
      const validAccount = {
        id: 'valid-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount
        .mockResolvedValueOnce(validAccount)
        .mockResolvedValueOnce(null) // Account not found

      const batchRequests: BatchSignRequest[] = [
        {
          accountId: 'valid-account',
          message: new TextEncoder().encode('valid message')
        },
        {
          accountId: 'invalid-account',
          message: new TextEncoder().encode('invalid message')
        }
      ]

      const results = await unifiedSigner.signBatch(batchRequests)

      expect(results).toHaveLength(2)
      expect(results[0].success).toBe(true)
      expect(results[0].accountId).toBe('valid-account')
      expect(results[1].success).toBe(false)
      expect(results[1].accountId).toBe('invalid-account')
    })

    test('should maintain order in batch results', async () => {
      const account = {
        id: 'test-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(account)

      const batchRequests: BatchSignRequest[] = [
        {
          accountId: 'test-account',
          message: new TextEncoder().encode('message 1')
        },
        {
          accountId: 'test-account',
          message: new TextEncoder().encode('message 2')
        },
        {
          accountId: 'test-account',
          message: new TextEncoder().encode('message 3')
        }
      ]

      const results = await unifiedSigner.signBatch(batchRequests)

      expect(results).toHaveLength(3)
      expect(results[0].batchIndex).toBe(0)
      expect(results[1].batchIndex).toBe(1)
      expect(results[2].batchIndex).toBe(2)
    })

    test('should group requests by platform for optimal processing', async () => {
      const pacificaAccount = {
        id: 'pac-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(pacificaAccount)

      const batchRequests: BatchSignRequest[] = [
        {
          accountId: 'pac-account',
          message: new TextEncoder().encode('message 1'),
          options: { forcePlatform: Platform.PACIFICA }
        },
        {
          accountId: 'pac-account',
          message: new TextEncoder().encode('message 2'),
          options: { forcePlatform: Platform.PACIFICA }
        }
      ]

      const results = await unifiedSigner.signBatch(batchRequests)

      expect(results).toHaveLength(2)
      expect(results.every(r => r.success)).toBe(true)
    })
  })

  describe('Performance Metrics', () => {
    test('should collect and provide metrics', async () => {
      const account = {
        id: 'test-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(account)

      // Reset metrics
      unifiedSigner.resetMetrics()

      const message = new TextEncoder().encode('metrics test')

      // Perform some operations
      await unifiedSigner.sign('test-account', message, { collectMetrics: true })
      await unifiedSigner.sign('test-account', message, { collectMetrics: true })

      const metrics = unifiedSigner.getMetrics()

      expect(metrics.totalOperations).toBe(2)
      expect(metrics.successfulOperations).toBe(2)
      expect(metrics.failedOperations).toBe(0)
      expect(metrics.averageSigningTime).toBeGreaterThan(0)
      expect(metrics.platformBreakdown[Platform.PACIFICA].operations).toBe(2)
      expect(metrics.platformBreakdown[Platform.PACIFICA].successRate).toBe(1)
    })

    test('should track failures in metrics', async () => {
      mockGetAccount.mockResolvedValue(null) // Always fail

      unifiedSigner.resetMetrics()

      const message = new TextEncoder().encode('failure test')

      // Perform failing operations
      await unifiedSigner.sign('non-existent', message, { collectMetrics: true })
      await unifiedSigner.sign('non-existent', message, { collectMetrics: true })

      const metrics = unifiedSigner.getMetrics()

      expect(metrics.totalOperations).toBe(2)
      expect(metrics.successfulOperations).toBe(0)
      expect(metrics.failedOperations).toBe(2)
    })

    test('should reset metrics correctly', () => {
      unifiedSigner.resetMetrics()

      const metrics = unifiedSigner.getMetrics()

      expect(metrics.totalOperations).toBe(0)
      expect(metrics.successfulOperations).toBe(0)
      expect(metrics.failedOperations).toBe(0)
      expect(metrics.averageSigningTime).toBe(0)
      expect(metrics.lastResetAt).toBeInstanceOf(Date)
    })

    test('should calculate platform-specific metrics correctly', async () => {
      const pacificaAccount = {
        id: 'pac-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      const asterAccount = {
        id: 'ast-account',
        platform: Platform.ASTER,
        credentials: {
          type: 'secp256k1' as const,
          privateKey: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
        }
      }

      mockGetAccount
        .mockResolvedValueOnce(pacificaAccount)
        .mockResolvedValueOnce(asterAccount)
        .mockResolvedValueOnce(null) // Failure

      unifiedSigner.resetMetrics()

      const message = new TextEncoder().encode('platform metrics test')

      await unifiedSigner.sign('pac-account', message, { collectMetrics: true })
      await unifiedSigner.sign('ast-account', message, { collectMetrics: true })
      await unifiedSigner.sign('non-existent', message, { collectMetrics: true })

      const metrics = unifiedSigner.getMetrics()

      expect(metrics.platformBreakdown[Platform.PACIFICA].operations).toBe(1)
      expect(metrics.platformBreakdown[Platform.PACIFICA].successRate).toBe(1)
      expect(metrics.platformBreakdown[Platform.ASTER].operations).toBe(1)
      expect(metrics.platformBreakdown[Platform.ASTER].successRate).toBe(1)
    })
  })

  describe('Timeout Handling', () => {
    test('should respect custom timeout settings', async () => {
      const slowAccount = {
        id: 'slow-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      // Mock a slow account getter
      mockGetAccount.mockImplementation(() =>
        new Promise(resolve => setTimeout(() => resolve(slowAccount), 100))
      )

      const message = new TextEncoder().encode('timeout test')

      // Test with very short timeout
      const result = await unifiedSigner.sign('slow-account', message, {
        timeout: 50 // 50ms timeout, but getter takes 100ms
      })

      expect(result.success).toBe(false)
      expect(result.error).toContain('timed out')
    })

    test('should use default timeout when not specified', async () => {
      const account = {
        id: 'normal-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(account)

      const message = new TextEncoder().encode('default timeout test')
      const result = await unifiedSigner.sign('normal-account', message)

      expect(result.success).toBe(true) // Should succeed with default timeout
    })
  })

  describe('Error Handling', () => {
    test('should handle account getter errors gracefully', async () => {
      mockGetAccount.mockRejectedValue(new Error('Database connection failed'))

      const message = new TextEncoder().encode('error test')
      const result = await unifiedSigner.sign('test-account', message)

      expect(result.success).toBe(false)
      expect(result.error).toContain('Database connection failed')
    })

    test('should handle signing errors gracefully', async () => {
      const invalidAccount = {
        id: 'invalid-account',
        platform: Platform.PACIFICA,
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'invalid-key' // This will cause signing to fail
        }
      }

      mockGetAccount.mockResolvedValue(invalidAccount)

      const message = new TextEncoder().encode('signing error test')
      const result = await unifiedSigner.sign('invalid-account', message)

      expect(result.success).toBe(false)
      expect(result.error).toBeDefined()
    })

    test('should provide meaningful error messages', async () => {
      const testCases = [
        {
          mockReturn: null,
          expectedError: 'Account not found'
        },
        {
          mockReturn: { id: 'test', credentials: {} },
          expectedError: 'Cannot determine platform'
        }
      ]

      for (const testCase of testCases) {
        mockGetAccount.mockResolvedValueOnce(testCase.mockReturn)

        const message = new TextEncoder().encode('error message test')
        const result = await unifiedSigner.sign('test-account', message)

        expect(result.success).toBe(false)
        expect(result.error).toContain(testCase.expectedError)
      }
    })
  })

  describe('Integration with Platform Detectors', () => {
    test('should use platform detector when no platform specified', async () => {
      const pacificaAccount = {
        id: 'auto-detect-account',
        credentials: {
          type: 'ed25519' as const,
          privateKey: 'f26670e2ca334117f8859f9f32e50251641953a30b54f6ffcf82db836cfdfea5'
        }
      }

      mockGetAccount.mockResolvedValue(pacificaAccount)

      const message = new TextEncoder().encode('auto-detect test')
      const result = await unifiedSigner.sign('auto-detect-account', message)

      expect(result.success).toBe(true)
      expect(result.metadata?.platform).toBe(Platform.PACIFICA)
    })

    test('should handle platform detection failures', async () => {
      const unknownAccount = {
        id: 'unknown-account',
        credentials: {
          type: 'unknown-type' as any,
          privateKey: 'some-key'
        }
      }

      mockGetAccount.mockResolvedValue(unknownAccount)

      const message = new TextEncoder().encode('detection failure test')
      const result = await unifiedSigner.sign('unknown-account', message)

      expect(result.success).toBe(false)
      expect(result.error).toContain('Cannot determine platform')
    })
  })
})