/**
 * Contract Test: Binance Signer Interface
 *
 * Tests the IBinanceSigner contract to ensure it correctly implements
 * HMAC-SHA256 signing for Binance API requests.
 */

import { describe, test, expect, beforeEach } from '@jest/globals';
import { Platform, SignResult, ErrorType } from '../../src/types/credential.js';

// ============================================================================
// Interface Contracts to Test
// ============================================================================

/**
 * Binance平台签名器接口
 */
interface IBinanceSigner {
  readonly platform: Platform.BINANCE;

  /**
   * 对Binance API请求进行签名
   * @param request Binance签名请求
   * @returns 签名结果，包含HMAC-SHA256签名
   */
  signRequest(request: BinanceSignRequest): Promise<BinanceSignResponse>;

  /**
   * 验证Binance签名
   * @param request 验证请求
   * @returns 验证结果
   */
  verifySignature(request: BinanceVerifyRequest): Promise<BinanceVerifyResponse>;

  /**
   * 获取账户API密钥
   * @param accountId 账户ID
   * @returns API密钥
   */
  getApiKey(accountId: string): Promise<string>;

  /**
   * 批量签名API请求
   * @param requests 批量签名请求
   * @returns 批量签名结果
   */
  signBatch(requests: BinanceSignRequest[]): Promise<BinanceSignResponse[]>;
}

// ============================================================================
// Request/Response Types
// ============================================================================

interface BinanceSignRequest {
  accountId: string;
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  endpoint: string;
  params?: Record<string, any>;
  options?: BinanceSignOptions;
}

interface BinanceSignOptions {
  timeout?: number;
  includeTimestamp?: boolean;
  recvWindow?: number;
}

interface BinanceSignResponse extends SignResult {
  signature: string;
  algorithm: 'hmac-sha256';
  apiKey: string;
  timestamp: number;
  queryString: string;
}

interface BinanceVerifyRequest {
  message: string;
  signature: string;
  secretKey: string;
  algorithm?: 'hmac-sha256';
}

interface BinanceVerifyResponse {
  valid: boolean;
  algorithm: 'hmac-sha256';
  timestamp: Date;
}

// ============================================================================
// Test Constants
// ============================================================================

const TEST_CREDENTIALS = {
  apiKey: 'test_binance_api_key_12345678901234567890',
  secretKey: 'test_binance_secret_key_12345678901234567890'
};

const SAMPLE_REQUESTS = {
  accountInfo: {
    method: 'GET' as const,
    endpoint: '/api/v3/account',
    params: { timestamp: 1640995200000, recvWindow: 5000 }
  },
  newOrder: {
    method: 'POST' as const,
    endpoint: '/api/v3/order',
    params: {
      symbol: 'BTCUSDT',
      side: 'BUY',
      type: 'LIMIT',
      timeInForce: 'GTC',
      quantity: '0.001',
      price: '50000.00',
      timestamp: 1640995200000
    }
  }
};

// ============================================================================
// Mock Implementation for Testing
// ============================================================================

class MockBinanceSigner implements IBinanceSigner {
  readonly platform = Platform.BINANCE;
  private accounts = new Map<string, any>();

  constructor() {
    // Add test account
    this.accounts.set('binance-test-001', {
      credentials: TEST_CREDENTIALS
    });
  }

  async signRequest(request: BinanceSignRequest): Promise<BinanceSignResponse> {
    const account = this.accounts.get(request.accountId);
    if (!account) {
      throw new Error(`Account ${request.accountId} not found`);
    }

    // Simulate HMAC-SHA256 signing
    const timestamp = Date.now();
    const queryString = this.buildQueryString(request.params || {}, timestamp);
    const signature = this.computeHmacSha256(queryString, account.credentials.secretKey);

    return {
      success: true,
      signature,
      algorithm: 'hmac-sha256',
      apiKey: account.credentials.apiKey,
      timestamp,
      queryString
    };
  }

  async verifySignature(request: BinanceVerifyRequest): Promise<BinanceVerifyResponse> {
    const expected = this.computeHmacSha256(request.message, request.secretKey);
    return {
      valid: expected === request.signature,
      algorithm: 'hmac-sha256',
      timestamp: new Date()
    };
  }

  async getApiKey(accountId: string): Promise<string> {
    const account = this.accounts.get(accountId);
    if (!account) {
      throw new Error(`Account ${accountId} not found`);
    }
    return account.credentials.apiKey;
  }

  async signBatch(requests: BinanceSignRequest[]): Promise<BinanceSignResponse[]> {
    return Promise.all(requests.map(req => this.signRequest(req)));
  }

  private buildQueryString(params: Record<string, any>, timestamp: number): string {
    const allParams = { ...params, timestamp };
    return Object.keys(allParams)
      .sort()
      .map(key => `${key}=${allParams[key]}`)
      .join('&');
  }

  private computeHmacSha256(message: string, secret: string): string {
    // Simulate HMAC-SHA256 computation
    return `hmac_sha256_${message.length}_${secret.length}_${Date.now()}`;
  }
}

// ============================================================================
// Contract Tests
// ============================================================================

describe('IBinanceSigner Contract Tests', () => {
  let signer: IBinanceSigner;

  beforeEach(() => {
    signer = new MockBinanceSigner();
  });

  describe('Platform Identification', () => {
    test('should identify as BINANCE platform', () => {
      expect(signer.platform).toBe(Platform.BINANCE);
    });
  });

  describe('Request Signing', () => {
    test('should sign GET request successfully', async () => {
      const request: BinanceSignRequest = {
        accountId: 'binance-test-001',
        ...SAMPLE_REQUESTS.accountInfo
      };

      const result = await signer.signRequest(request);

      expect(result.success).toBe(true);
      expect(result.signature).toBeDefined();
      expect(result.algorithm).toBe('hmac-sha256');
      expect(result.apiKey).toBe(TEST_CREDENTIALS.apiKey);
      expect(result.timestamp).toBeGreaterThan(0);
      expect(result.queryString).toContain('timestamp=');
    });

    test('should sign POST request successfully', async () => {
      const request: BinanceSignRequest = {
        accountId: 'binance-test-001',
        ...SAMPLE_REQUESTS.newOrder
      };

      const result = await signer.signRequest(request);

      expect(result.success).toBe(true);
      expect(result.signature).toBeDefined();
      expect(result.algorithm).toBe('hmac-sha256');
      expect(result.queryString).toContain('symbol=BTCUSDT');
    });

    test('should fail for non-existent account', async () => {
      const request: BinanceSignRequest = {
        accountId: 'non-existent-account',
        ...SAMPLE_REQUESTS.accountInfo
      };

      await expect(signer.signRequest(request)).rejects.toThrow('Account non-existent-account not found');
    });

    test('should handle empty params', async () => {
      const request: BinanceSignRequest = {
        accountId: 'binance-test-001',
        method: 'GET',
        endpoint: '/api/v3/time'
      };

      const result = await signer.signRequest(request);

      expect(result.success).toBe(true);
      expect(result.queryString).toContain('timestamp=');
    });
  });

  describe('Signature Verification', () => {
    test('should verify valid signature', async () => {
      const message = 'symbol=BTCUSDT&side=BUY&timestamp=1640995200000';
      const signature = `hmac_sha256_${message.length}_${TEST_CREDENTIALS.secretKey.length}_${Date.now()}`;

      const request: BinanceVerifyRequest = {
        message,
        signature,
        secretKey: TEST_CREDENTIALS.secretKey
      };

      const result = await signer.verifySignature(request);

      expect(result.valid).toBe(true);
      expect(result.algorithm).toBe('hmac-sha256');
      expect(result.timestamp).toBeInstanceOf(Date);
    });

    test('should reject invalid signature', async () => {
      const request: BinanceVerifyRequest = {
        message: 'symbol=BTCUSDT&side=BUY&timestamp=1640995200000',
        signature: 'invalid_signature',
        secretKey: TEST_CREDENTIALS.secretKey
      };

      const result = await signer.verifySignature(request);

      expect(result.valid).toBe(false);
      expect(result.algorithm).toBe('hmac-sha256');
    });
  });

  describe('API Key Management', () => {
    test('should return API key for valid account', async () => {
      const apiKey = await signer.getApiKey('binance-test-001');

      expect(apiKey).toBe(TEST_CREDENTIALS.apiKey);
    });

    test('should fail for non-existent account', async () => {
      await expect(signer.getApiKey('non-existent-account')).rejects.toThrow('Account non-existent-account not found');
    });
  });

  describe('Batch Operations', () => {
    test('should handle batch signing', async () => {
      const requests: BinanceSignRequest[] = [
        {
          accountId: 'binance-test-001',
          ...SAMPLE_REQUESTS.accountInfo
        },
        {
          accountId: 'binance-test-001',
          ...SAMPLE_REQUESTS.newOrder
        }
      ];

      const results = await signer.signBatch(requests);

      expect(results).toHaveLength(2);
      expect(results[0].success).toBe(true);
      expect(results[1].success).toBe(true);
      expect(results[0].algorithm).toBe('hmac-sha256');
      expect(results[1].algorithm).toBe('hmac-sha256');
    });

    test('should handle empty batch', async () => {
      const results = await signer.signBatch([]);

      expect(results).toHaveLength(0);
    });
  });

  describe('Performance Requirements', () => {
    test('should complete signing within 50ms', async () => {
      const request: BinanceSignRequest = {
        accountId: 'binance-test-001',
        ...SAMPLE_REQUESTS.accountInfo
      };

      const startTime = Date.now();
      const result = await signer.signRequest(request);
      const duration = Date.now() - startTime;

      expect(result.success).toBe(true);
      expect(duration).toBeLessThan(50);
    });

    test('should handle concurrent requests', async () => {
      const requests = Array.from({ length: 10 }, () => ({
        accountId: 'binance-test-001',
        ...SAMPLE_REQUESTS.accountInfo
      }));

      const startTime = Date.now();
      const results = await Promise.all(
        requests.map(req => signer.signRequest(req))
      );
      const duration = Date.now() - startTime;

      expect(results).toHaveLength(10);
      expect(results.every(r => r.success)).toBe(true);
      expect(duration).toBeLessThan(200); // 10 concurrent requests < 200ms
    });
  });

  describe('Error Handling', () => {
    test('should handle malformed request gracefully', async () => {
      const request = {
        accountId: 'binance-test-001',
        method: 'INVALID' as any,
        endpoint: '/api/v3/account'
      };

      // Should not throw, but might return error in response
      try {
        const result = await signer.signRequest(request);
        // If it succeeds, check success flag
        if (!result.success) {
          expect(result.error).toBeDefined();
        }
      } catch (error) {
        // If it throws, that's also acceptable
        expect(error).toBeInstanceOf(Error);
      }
    });

    test('should handle large parameter sets', async () => {
      const largeParams = Array.from({ length: 100 }, (_, i) => [`param${i}`, `value${i}`])
        .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {});

      const request: BinanceSignRequest = {
        accountId: 'binance-test-001',
        method: 'POST',
        endpoint: '/api/v3/order',
        params: largeParams
      };

      const result = await signer.signRequest(request);

      expect(result.success).toBe(true);
      expect(result.queryString.length).toBeGreaterThan(100);
    });
  });
});

// ============================================================================
// Integration Hints for Implementation
// ============================================================================

/**
 * Implementation Notes:
 *
 * 1. HMAC-SHA256 Algorithm:
 *    - Use Node.js crypto module for HMAC-SHA256
 *    - Query string format: key1=value1&key2=value2 (sorted by key)
 *    - Always include timestamp parameter
 *
 * 2. Performance Requirements:
 *    - Individual signing: < 50ms
 *    - Batch operations: optimize for concurrent requests
 *    - Use caching for repeated computations
 *
 * 3. Error Handling:
 *    - Validate API key/secret format
 *    - Handle network timeouts gracefully
 *    - Log all signing attempts for debugging
 *
 * 4. Security Considerations:
 *    - Never log secret keys
 *    - Validate request parameters
 *    - Implement request rate limiting
 *
 * 5. Binance API Specifics:
 *    - recvWindow parameter for timing tolerance
 *    - Different endpoints may have different param requirements
 *    - Some endpoints don't require signatures
 */