import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
import { Price } from '@modules/listings/domain/value-objects/price.vo';
import { FeatureListingCommand } from '../commands/feature-listing/feature-listing.command';
import { FeatureListingHandler } from '../commands/feature-listing/feature-listing.handler';

function createListing(
  id = 'listing-1',
  sellerId = 'seller-1',
  agentId: string | null = null,
  status: 'DRAFT' | 'PENDING_REVIEW' | 'ACTIVE' = 'ACTIVE',
): ListingEntity {
  const price = Price.create(2_000_000_000n).unwrap();
  const listing = ListingEntity.createNew(id, 'prop-1', sellerId, 'SALE', price, 80, agentId ?? undefined);
  if (status === 'PENDING_REVIEW' || status === 'ACTIVE') listing.submitForReview();
  if (status === 'ACTIVE') listing.approve();
  listing.clearDomainEvents();
  return listing;
}

describe('FeatureListingHandler', () => {
  let handler: FeatureListingHandler;
  let mockListingRepo: Pick<IListingRepository, 'findById'>;
  let mockPaymentInitiator: { initiate: ReturnType<typeof vi.fn> };
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };
  let mockLogger: { log: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockListingRepo = { findById: vi.fn() };
    mockPaymentInitiator = {
      initiate: vi.fn().mockResolvedValue({
        paymentId: 'pay-1',
        paymentUrl: 'https://pay.example.com/checkout',
        providerTxId: 'tx-1',
      }),
    };
    mockEventBus = { publish: vi.fn() };
    mockLogger = { log: vi.fn(), error: vi.fn() };

    handler = new FeatureListingHandler(
      mockListingRepo as any,
      mockPaymentInitiator as any,
      mockEventBus as any,
      mockLogger as any,
    );
  });

  it('creates payment for a valid feature request', async () => {
    const listing = createListing('listing-1', 'seller-1', null, 'ACTIVE');
    (mockListingRepo.findById as ReturnType<typeof vi.fn>).mockResolvedValue(listing);

    const command = new FeatureListingCommand(
      'listing-1', 'seller-1', '7_days', 'VNPAY',
      'https://goodgo.vn/callback', '127.0.0.1',
    );
    const result = await handler.execute(command);

    expect(result.paymentId).toBe('pay-1');
    expect(result.paymentUrl).toBe('https://pay.example.com/checkout');
    expect(result.package_).toBe('7_days');
    expect(result.priceVND).toBe('199000');
    expect(mockPaymentInitiator.initiate).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish.mock.calls[0]?.[0]?.eventName).toBe('listing.featured-payment-requested');
  });

  it('allows the assigned agent to feature the listing', async () => {
    const listing = createListing('listing-1', 'seller-1', 'agent-1', 'ACTIVE');
    (mockListingRepo.findById as ReturnType<typeof vi.fn>).mockResolvedValue(listing);

    const command = new FeatureListingCommand(
      'listing-1', 'agent-1', '3_days', 'MOMO',
      'https://goodgo.vn/callback', '127.0.0.1',
    );
    const result = await handler.execute(command);

    expect(result.paymentId).toBe('pay-1');
    expect(result.priceVND).toBe('99000');
  });

  it('rejects feature request from unauthorized user', async () => {
    const listing = createListing('listing-1', 'seller-1', null, 'ACTIVE');
    (mockListingRepo.findById as ReturnType<typeof vi.fn>).mockResolvedValue(listing);

    const command = new FeatureListingCommand(
      'listing-1', 'stranger', '7_days', 'VNPAY',
      'https://goodgo.vn/callback', '127.0.0.1',
    );

    await expect(handler.execute(command)).rejects.toThrow(/người bán|môi giới/);
  });

  it('rejects feature request for non-ACTIVE listing', async () => {
    const listing = createListing('listing-1', 'seller-1', null, 'DRAFT');
    (mockListingRepo.findById as ReturnType<typeof vi.fn>).mockResolvedValue(listing);

    const command = new FeatureListingCommand(
      'listing-1', 'seller-1', '7_days', 'VNPAY',
      'https://goodgo.vn/callback', '127.0.0.1',
    );

    await expect(handler.execute(command)).rejects.toThrow(/hoạt động/);
  });

  it('throws NotFoundException for non-existent listing', async () => {
    (mockListingRepo.findById as ReturnType<typeof vi.fn>).mockResolvedValue(null);

    const command = new FeatureListingCommand(
      'nonexistent', 'seller-1', '7_days', 'VNPAY',
      'https://goodgo.vn/callback', '127.0.0.1',
    );

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

  it('uses correct pricing for each package', async () => {
    const listing = createListing('listing-1', 'seller-1', null, 'ACTIVE');
    (mockListingRepo.findById as ReturnType<typeof vi.fn>).mockResolvedValue(listing);

    for (const [pkg, expectedPrice] of [
      ['3_days', '99000'],
      ['7_days', '199000'],
      ['30_days', '499000'],
    ] as const) {
      const command = new FeatureListingCommand(
        'listing-1', 'seller-1', pkg, 'VNPAY',
        'https://goodgo.vn/callback', '127.0.0.1',
      );
      const result = await handler.execute(command);
      expect(result.priceVND).toBe(expectedPrice);
    }
  });
});
