import { describe, it, expect } from 'vitest';
import { PaymentEntity } from '../entities/payment.entity';
import { PaymentCompletedEvent } from '../events/payment-completed.event';
import { PaymentCreatedEvent } from '../events/payment-created.event';
import { PaymentFailedEvent } from '../events/payment-failed.event';
import { PaymentRefundedEvent } from '../events/payment-refunded.event';
import { Money } from '../value-objects/money.vo';

describe('PaymentEntity', () => {
  const createPayment = (status?: string) => {
    const money = Money.create(500_000n).unwrap();
    const payment = PaymentEntity.createNew(
      'pay-1',
      'user-1',
      'VNPAY',
      'LISTING_FEE',
      money,
      'txn-1',
      'idem-key-1',
    );
    if (status === 'PROCESSING') {
      payment.markProcessing('vnp-txn-123');
    }
    if (status === 'COMPLETED') {
      payment.markProcessing('vnp-txn-123');
      payment.clearDomainEvents();
      payment.markCompleted({ responseCode: '00' });
    }
    return payment;
  };

  it('should create a new payment with domain events', () => {
    const money = Money.create(500_000n).unwrap();
    const payment = PaymentEntity.createNew(
      'pay-1',
      'user-1',
      'VNPAY',
      'LISTING_FEE',
      money,
      'txn-1',
    );

    expect(payment.id).toBe('pay-1');
    expect(payment.userId).toBe('user-1');
    expect(payment.provider).toBe('VNPAY');
    expect(payment.type).toBe('LISTING_FEE');
    expect(payment.amount.value).toBe(500_000n);
    expect(payment.status).toBe('PENDING');
    expect(payment.transactionId).toBe('txn-1');
    expect(payment.providerTxId).toBeNull();

    const events = payment.domainEvents;
    expect(events).toHaveLength(1);
    expect(events[0]).toBeInstanceOf(PaymentCreatedEvent);
  });

  it('should mark payment as processing', () => {
    const payment = createPayment();
    payment.markProcessing('vnp-txn-123');

    expect(payment.status).toBe('PROCESSING');
    expect(payment.providerTxId).toBe('vnp-txn-123');
  });

  it('should mark payment as completed from PENDING', () => {
    const payment = createPayment();
    payment.clearDomainEvents();
    payment.markCompleted({ responseCode: '00' });

    expect(payment.status).toBe('COMPLETED');
    expect(payment.callbackData).toEqual({ responseCode: '00' });

    const events = payment.domainEvents;
    expect(events).toHaveLength(1);
    expect(events[0]).toBeInstanceOf(PaymentCompletedEvent);
  });

  it('should mark payment as completed from PROCESSING', () => {
    const payment = createPayment('PROCESSING');
    payment.clearDomainEvents();
    payment.markCompleted({ responseCode: '00' });

    expect(payment.status).toBe('COMPLETED');
  });

  it('should mark payment as failed', () => {
    const payment = createPayment();
    payment.clearDomainEvents();
    payment.markFailed({ responseCode: '99' });

    expect(payment.status).toBe('FAILED');
    const events = payment.domainEvents;
    expect(events).toHaveLength(1);
    expect(events[0]).toBeInstanceOf(PaymentFailedEvent);
  });

  it('should not complete an already completed payment', () => {
    const payment = createPayment('COMPLETED');
    const result = payment.markCompleted({});
    expect(result.isErr).toBe(true);
    expect(result.unwrapErr().message).toContain('Cannot complete payment');
  });

  it('should not fail an already completed payment', () => {
    const payment = createPayment('COMPLETED');
    const result = payment.markFailed({});
    expect(result.isErr).toBe(true);
    expect(result.unwrapErr().message).toContain('Cannot fail payment');
  });

  it('should mark completed payment as refunded and emit event', () => {
    const payment = createPayment('COMPLETED');
    payment.clearDomainEvents();
    const result = payment.markRefunded();
    expect(result.isOk).toBe(true);
    expect(payment.status).toBe('REFUNDED');

    const events = payment.domainEvents;
    expect(events).toHaveLength(1);
    expect(events[0]).toBeInstanceOf(PaymentRefundedEvent);
  });

  it('should not refund a non-completed payment', () => {
    const payment = createPayment();
    const result = payment.markRefunded();
    expect(result.isErr).toBe(true);
    expect(result.unwrapErr().message).toContain('hoàn tiền');
  });

  it('should store idempotency key', () => {
    const money = Money.create(100_000n).unwrap();
    const payment = PaymentEntity.createNew(
      'pay-2',
      'user-1',
      'MOMO',
      'SUBSCRIPTION',
      money,
      undefined,
      'unique-key-123',
    );
    expect(payment.idempotencyKey).toBe('unique-key-123');
  });
});
