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

describe('VnpayService', () => {
  let service: VnpayService;

  beforeEach(() => {
    const mockConfig = {
      get: vi.fn((key: string, defaultValue?: string) => {
        const env: Record<string, string> = {
          VNPAY_TMN_CODE: 'TESTCODE',
          VNPAY_HASH_SECRET: 'TESTSECRET123456TESTSECRET123456',
        };
        return env[key] ?? defaultValue;
      }),
      getOrThrow: vi.fn((key: string) => {
        const env: Record<string, string> = {
          VNPAY_TMN_CODE: 'TESTCODE',
          VNPAY_HASH_SECRET: 'TESTSECRET123456TESTSECRET123456',
        };
        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 VnpayService(mockConfig, mockLogger as any);
  });

  it('should create a payment URL', 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('vnp_TmnCode=TESTCODE');
    expect(result.paymentUrl).toContain('vnp_Amount=50000000');
    expect(result.paymentUrl).toContain('vnp_TxnRef=order-123');
    expect(result.paymentUrl).toContain('vnp_SecureHash=');
    expect(result.providerTxId).toBe('order-123');
  });

  it('should verify a valid callback', () => {
    // First create a payment URL to get the hash
    const params: Record<string, string> = {
      vnp_TmnCode: 'TESTCODE',
      vnp_Amount: '50000000',
      vnp_TxnRef: 'order-123',
      vnp_ResponseCode: '00',
      vnp_TransactionNo: 'VNP123',
      vnp_OrderInfo: 'Test',
    };

    // Generate valid hash
    const sorted = Object.keys(params)
      .sort()
      .reduce((acc: Record<string, string>, key) => {
        acc[key] = params[key]!;
        return acc;
      }, {});
    const signData = new URLSearchParams(sorted).toString();
    const hmac = crypto.createHmac('sha512', 'TESTSECRET123456TESTSECRET123456');
    const signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');

    params['vnp_SecureHash'] = signed;

    const result = service.verifyCallback(params);

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

  it('should reject invalid callback signature', () => {
    const params: Record<string, string> = {
      vnp_TmnCode: 'TESTCODE',
      vnp_Amount: '50000000',
      vnp_TxnRef: 'order-123',
      vnp_ResponseCode: '00',
      vnp_TransactionNo: 'VNP123',
      vnp_SecureHash: 'invalid-hash',
    };

    const result = service.verifyCallback(params);
    expect(result.isValid).toBe(false);
    expect(result.isSuccess).toBe(false);
  });

  it('should detect failed payment in callback', () => {
    const params: Record<string, string> = {
      vnp_TmnCode: 'TESTCODE',
      vnp_Amount: '50000000',
      vnp_TxnRef: 'order-123',
      vnp_ResponseCode: '24', // User cancelled
      vnp_TransactionNo: 'VNP123',
    };

    const sorted = Object.keys(params)
      .sort()
      .reduce((acc: Record<string, string>, key) => {
        acc[key] = params[key]!;
        return acc;
      }, {});
    const signData = new URLSearchParams(sorted).toString();
    const hmac = crypto.createHmac('sha512', 'TESTSECRET123456TESTSECRET123456');
    const signed = hmac.update(Buffer.from(signData, 'utf-8')).digest('hex');

    params['vnp_SecureHash'] = signed;

    const result = service.verifyCallback(params);
    expect(result.isValid).toBe(true);
    expect(result.isSuccess).toBe(false);
  });
});
