import { type IPaymentRepository } from '../../domain/repositories/payment.repository';
import { CreatePaymentCommand } from '../commands/create-payment/create-payment.command';
import { CreatePaymentHandler } from '../commands/create-payment/create-payment.handler';

describe('CreatePaymentHandler', () => {
  let handler: CreatePaymentHandler;
  let mockPaymentRepo: { [K in keyof IPaymentRepository]: ReturnType<typeof vi.fn> };
  let mockGatewayFactory: { getGateway: ReturnType<typeof vi.fn> };
  let mockGateway: { createPaymentUrl: ReturnType<typeof vi.fn>; verifyCallback: ReturnType<typeof vi.fn>; refund: ReturnType<typeof vi.fn> };
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockPaymentRepo = {
      findById: vi.fn(),
      findByProviderTxId: vi.fn(),
      findByIdempotencyKey: vi.fn(),
      findByUserId: vi.fn(),
      save: vi.fn().mockResolvedValue(undefined),
      update: vi.fn(),
      updateIfStatus: vi.fn(),
    };

    mockGateway = {
      createPaymentUrl: vi.fn().mockResolvedValue({
        paymentUrl: 'https://vnpay.vn/pay/123',
        providerTxId: 'vnpay-tx-1',
      }),
      verifyCallback: vi.fn(),
      refund: vi.fn(),
    };

    mockGatewayFactory = {
      getGateway: vi.fn().mockReturnValue(mockGateway),
    };

    mockEventBus = { publish: vi.fn() };

    const mockLogger = { log: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn(), verbose: vi.fn() };

    handler = new CreatePaymentHandler(
      mockPaymentRepo as any,
      mockGatewayFactory as any,
      mockEventBus as any,
      mockLogger as any,
    );
  });

  it('creates payment successfully', async () => {
    mockPaymentRepo.findByIdempotencyKey.mockResolvedValue(null);

    const command = new CreatePaymentCommand(
      'user-1', 'VNPAY', 'SUBSCRIPTION', 500_000n,
      'Thanh toán gói Pro', 'https://goodgo.vn/return', '127.0.0.1',
      undefined, 'idem-key-1',
    );
    const result = await handler.execute(command);

    expect(result.paymentId).toBeDefined();
    expect(result.paymentUrl).toBe('https://vnpay.vn/pay/123');
    expect(result.providerTxId).toBe('vnpay-tx-1');
    expect(mockPaymentRepo.save).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalled();
    expect(mockGatewayFactory.getGateway).toHaveBeenCalledWith('VNPAY');
  });

  it('throws ConflictException for duplicate idempotency key (pending)', async () => {
    mockPaymentRepo.findByIdempotencyKey.mockResolvedValue({ status: 'PENDING' });

    const command = new CreatePaymentCommand(
      'user-1', 'VNPAY', 'SUBSCRIPTION', 500_000n,
      'desc', 'https://goodgo.vn/return', '127.0.0.1',
      undefined, 'existing-key',
    );

    await expect(handler.execute(command)).rejects.toThrow(/idempotency/);
  });

  it('throws ConflictException for already processed idempotency key', async () => {
    mockPaymentRepo.findByIdempotencyKey.mockResolvedValue({ status: 'COMPLETED' });

    const command = new CreatePaymentCommand(
      'user-1', 'VNPAY', 'SUBSCRIPTION', 500_000n,
      'desc', 'https://goodgo.vn/return', '127.0.0.1',
      undefined, 'completed-key',
    );

    await expect(handler.execute(command)).rejects.toThrow(/xử lý/);
  });

  it('throws ValidationException for invalid amount', async () => {
    const command = new CreatePaymentCommand(
      'user-1', 'VNPAY', 'SUBSCRIPTION', -100n,
      'desc', 'https://goodgo.vn/return', '127.0.0.1',
    );

    await expect(handler.execute(command)).rejects.toThrow();
  });

  it('creates payment without idempotency key', async () => {
    const command = new CreatePaymentCommand(
      'user-1', 'VNPAY', 'DEPOSIT', 1_000_000n,
      'Nạp tiền', 'https://goodgo.vn/return', '127.0.0.1',
    );
    const result = await handler.execute(command);

    expect(result.paymentId).toBeDefined();
    expect(mockPaymentRepo.findByIdempotencyKey).not.toHaveBeenCalled();
  });
});
