import * as crypto from 'crypto';
import { type ConfigService } from '@nestjs/config';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { BankTransferService } from '../services/bank-transfer.service';

describe('BankTransferService', () => {
  let service: BankTransferService;
  const webhookSecret = 'test-webhook-secret-key-12345678';

  beforeEach(() => {
    const mockConfig = {
      get: vi.fn((key: string, defaultValue?: string) => {
        const env: Record<string, string> = {
          BANK_TRANSFER_ACCOUNT_NUMBER: '0123456789',
          BANK_TRANSFER_BANK_NAME: 'Vietcombank',
          BANK_TRANSFER_ACCOUNT_HOLDER: 'CONG TY GOODGO',
          BANK_TRANSFER_WEBHOOK_SECRET: webhookSecret,
          BANK_TRANSFER_INSTRUCTIONS_URL: 'https://goodgo.vn/thanh-toan/chuyen-khoan',
        };
        return env[key] ?? defaultValue;
      }),
      getOrThrow: vi.fn((key: string) => {
        const env: Record<string, string> = {
          BANK_TRANSFER_ACCOUNT_NUMBER: '0123456789',
          BANK_TRANSFER_BANK_NAME: 'Vietcombank',
          BANK_TRANSFER_ACCOUNT_HOLDER: 'CONG TY GOODGO',
          BANK_TRANSFER_WEBHOOK_SECRET: webhookSecret,
        };
        if (!env[key]) throw new Error(`Missing ${key}`);
        return env[key];
      }),
    } as unknown as ConfigService;
    const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };
    service = new BankTransferService(mockConfig, mockLogger as any);
  });

  describe('provider', () => {
    it('should have BANK_TRANSFER provider', () => {
      expect(service.provider).toBe('BANK_TRANSFER');
    });
  });

  describe('createPaymentUrl', () => {
    it('should return a payment URL with transfer instructions', async () => {
      const result = await service.createPaymentUrl({
        orderId: 'order-123',
        amountVND: 500_000n,
        description: 'Test payment',
        returnUrl: 'https://goodgo.vn/callback',
        ipAddress: '127.0.0.1',
      });

      expect(result.paymentUrl).toContain('https://goodgo.vn/thanh-toan/chuyen-khoan');
      expect(result.paymentUrl).toContain('ref=BT-order-123');
      expect(result.paymentUrl).toContain('amount=500000');
      expect(result.paymentUrl).toContain('bank=Vietcombank');
      expect(result.paymentUrl).toContain('account=0123456789');
      expect(result.paymentUrl).toContain('holder=CONG+TY+GOODGO');
      expect(result.paymentUrl).toContain('content=GG+order-123');
      expect(result.providerTxId).toBe('BT-order-123');
    });

    it('should generate unique providerTxId per order', async () => {
      const result1 = await service.createPaymentUrl({
        orderId: 'order-1',
        amountVND: 100_000n,
        description: 'Payment 1',
        returnUrl: 'https://goodgo.vn/callback',
        ipAddress: '127.0.0.1',
      });
      const result2 = await service.createPaymentUrl({
        orderId: 'order-2',
        amountVND: 200_000n,
        description: 'Payment 2',
        returnUrl: 'https://goodgo.vn/callback',
        ipAddress: '127.0.0.1',
      });

      expect(result1.providerTxId).toBe('BT-order-1');
      expect(result2.providerTxId).toBe('BT-order-2');
      expect(result1.providerTxId).not.toBe(result2.providerTxId);
    });
  });

  describe('verifyCallback', () => {
    function generateSignature(orderId: string, providerTxId: string, status: string, confirmedBy: string): string {
      const canonicalString = [orderId, providerTxId, status, confirmedBy].join('|');
      const hmac = crypto.createHmac('sha256', webhookSecret);
      return hmac.update(Buffer.from(canonicalString, 'utf-8')).digest('hex');
    }

    it('should verify a valid confirmed callback', () => {
      const orderId = 'order-123';
      const providerTxId = 'BT-order-123';
      const status = 'CONFIRMED';
      const confirmedBy = 'admin-user-1';
      const signature = generateSignature(orderId, providerTxId, status, confirmedBy);

      const result = service.verifyCallback({
        orderId,
        providerTxId,
        status,
        confirmedBy,
        signature,
      });

      expect(result.isValid).toBe(true);
      expect(result.isSuccess).toBe(true);
      expect(result.orderId).toBe('order-123');
      expect(result.providerTxId).toBe('BT-order-123');
    });

    it('should reject an invalid signature', () => {
      const result = service.verifyCallback({
        orderId: 'order-123',
        providerTxId: 'BT-order-123',
        status: 'CONFIRMED',
        confirmedBy: 'admin-user-1',
        signature: 'invalid-signature-value',
      });

      expect(result.isValid).toBe(false);
      expect(result.isSuccess).toBe(false);
    });

    it('should reject when status is not CONFIRMED', () => {
      const orderId = 'order-123';
      const providerTxId = 'BT-order-123';
      const status = 'REJECTED';
      const confirmedBy = 'admin-user-1';
      const signature = generateSignature(orderId, providerTxId, status, confirmedBy);

      const result = service.verifyCallback({
        orderId,
        providerTxId,
        status,
        confirmedBy,
        signature,
      });

      expect(result.isValid).toBe(true);
      expect(result.isSuccess).toBe(false);
    });

    it('should reject when signature is empty', () => {
      const result = service.verifyCallback({
        orderId: 'order-123',
        providerTxId: 'BT-order-123',
        status: 'CONFIRMED',
        confirmedBy: 'admin-user-1',
        signature: '',
      });

      expect(result.isValid).toBe(false);
      expect(result.isSuccess).toBe(false);
    });

    it('should handle missing fields gracefully', () => {
      const result = service.verifyCallback({});

      expect(result.isValid).toBe(false);
      expect(result.isSuccess).toBe(false);
      expect(result.orderId).toBe('');
      expect(result.providerTxId).toBe('');
    });
  });

  describe('refund', () => {
    it('should return unsuccessful result (manual processing required)', async () => {
      const result = await service.refund({
        providerTxId: 'BT-order-123',
        amountVND: 500_000n,
        reason: 'Customer refund request',
      });

      expect(result.success).toBe(false);
      expect(result.refundTxId).toBeNull();
    });
  });

  describe('generateConfirmationSignature', () => {
    it('should produce consistent signatures', () => {
      const sig1 = service.generateConfirmationSignature('order-1', 'BT-order-1', 'CONFIRMED', 'admin');
      const sig2 = service.generateConfirmationSignature('order-1', 'BT-order-1', 'CONFIRMED', 'admin');

      expect(sig1).toBe(sig2);
      expect(sig1).toHaveLength(64); // SHA-256 hex digest
    });

    it('should produce different signatures for different inputs', () => {
      const sig1 = service.generateConfirmationSignature('order-1', 'BT-order-1', 'CONFIRMED', 'admin');
      const sig2 = service.generateConfirmationSignature('order-2', 'BT-order-2', 'CONFIRMED', 'admin');

      expect(sig1).not.toBe(sig2);
    });

    it('should produce a signature that verifyCallback validates', () => {
      const orderId = 'order-456';
      const providerTxId = 'BT-order-456';
      const status = 'CONFIRMED';
      const confirmedBy = 'admin-user-2';

      const signature = service.generateConfirmationSignature(orderId, providerTxId, status, confirmedBy);
      const result = service.verifyCallback({ orderId, providerTxId, status, confirmedBy, signature });

      expect(result.isValid).toBe(true);
      expect(result.isSuccess).toBe(true);
    });
  });

  describe('getBankTransferInfo', () => {
    it('should return configured bank transfer information', () => {
      const info = service.getBankTransferInfo();

      expect(info.bankName).toBe('Vietcombank');
      expect(info.accountNumber).toBe('0123456789');
      expect(info.accountHolder).toBe('CONG TY GOODGO');
    });
  });
});
