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

describe('ZalopayService', () => {
  let service: ZalopayService;
  const key2 = 'TESTKEY2ABCDEF1234567890ABCDEF12';
  const callbackBaseUrl = 'https://api.goodgo.vn';

  beforeEach(() => {
    const mockConfig = {
      get: vi.fn((key: string, defaultValue?: string) => {
        const env: Record<string, string> = {
          'ZALOPAY_APP_ID': '2553',
          'ZALOPAY_KEY1': 'TESTKEY1ABCDEF1234567890ABCDEF12',
          'ZALOPAY_KEY2': 'TESTKEY2ABCDEF1234567890ABCDEF12',
          'PAYMENT_CALLBACK_BASE_URL': callbackBaseUrl,
        };
        return env[key] ?? defaultValue;
      }),
      getOrThrow: vi.fn((key: string) => {
        const env: Record<string, string> = {
          'ZALOPAY_APP_ID': '2553',
          'ZALOPAY_KEY1': 'TESTKEY1ABCDEF1234567890ABCDEF12',
          'ZALOPAY_KEY2': 'TESTKEY2ABCDEF1234567890ABCDEF12',
        };
        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 ZalopayService(mockConfig, mockLogger as any);
  });

  function buildCallbackData(
    dataPayload: Record<string, unknown> = {},
    tamperMac = false,
  ): Record<string, string> {
    const payload = {
      app_id: 2553,
      app_trans_id: '260408_order-123',
      zp_trans_id: 'ZLP_TX_456',
      ...dataPayload,
    };
    const dataStr = JSON.stringify(payload);
    let mac = crypto
      .createHmac('sha256', key2)
      .update(dataStr)
      .digest('hex');

    if (tamperMac) {
      mac = 'a'.repeat(mac.length);
    }

    return { data: dataStr, mac };
  }

  it('should verify a valid callback with timingSafeEqual', () => {
    const data = buildCallbackData();
    const result = service.verifyCallback(data);

    expect(result.isValid).toBe(true);
    expect(result.isSuccess).toBe(true);
    expect(result.orderId).toBe('260408_order-123');
    expect(result.providerTxId).toBe('ZLP_TX_456');
  });

  it('should reject an invalid MAC', () => {
    const data = buildCallbackData({}, true);
    const result = service.verifyCallback(data);

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

  it('should reject an empty MAC', () => {
    const data = buildCallbackData();
    data['mac'] = '';

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

  it('should reject a MAC with wrong length', () => {
    const data = buildCallbackData();
    data['mac'] = 'deadbeef'; // too short

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

  it('should handle invalid JSON in data field gracefully', () => {
    const mac = crypto
      .createHmac('sha256', key2)
      .update('not-json')
      .digest('hex');

    const result = service.verifyCallback({ data: 'not-json', mac });
    // timingSafeEqual passes but JSON.parse fails
    expect(result.isValid).toBe(false);
    expect(result.isSuccess).toBe(false);
  });

  it('createPaymentUrl should use backend URL for callback_url and frontend URL in embed_data.redirecturl', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: async () => ({ return_code: 1, order_url: 'https://zalopay.vn/pay', zp_trans_token: 'tok' }),
    });
    vi.stubGlobal('fetch', fetchMock);

    const returnUrl = 'https://goodgo.vn/payment/return';
    await service.createPaymentUrl({
      orderId: 'order-xyz',
      amountVND: 100000n,
      description: 'Test',
      returnUrl,
      ipAddress: '127.0.0.1',
    });

    expect(fetchMock).toHaveBeenCalledOnce();
    const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
    expect(body.callback_url).toBe(`${callbackBaseUrl}/api/v1/payments/callback/zalopay`);
    expect(body.callback_url).not.toBe(returnUrl);
    const embedData = JSON.parse(body.embed_data as string);
    expect(embedData.redirecturl).toBe(returnUrl);

    vi.unstubAllGlobals();
  });

  it('createPaymentUrl should use provided callbackUrl for callback_url when supplied', async () => {
    const fetchMock = vi.fn().mockResolvedValue({
      json: async () => ({ return_code: 1, order_url: 'https://zalopay.vn/pay', zp_trans_token: 'tok' }),
    });
    vi.stubGlobal('fetch', fetchMock);

    const customCallback = 'https://staging-api.goodgo.vn/api/v1/payments/callback/zalopay';
    await service.createPaymentUrl({
      orderId: 'order-xyz',
      amountVND: 100000n,
      description: 'Test',
      returnUrl: 'https://goodgo.vn/payment/return',
      callbackUrl: customCallback,
      ipAddress: '127.0.0.1',
    });

    const body = JSON.parse(fetchMock.mock.calls[0][1].body as string);
    expect(body.callback_url).toBe(customCallback);

    vi.unstubAllGlobals();
  });
});
