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 { RejectListingCommand } from '../commands/reject-listing/reject-listing.command';
import { RejectListingHandler } from '../commands/reject-listing/reject-listing.handler';

function createPendingListing(id = 'listing-1'): ListingEntity {
  const price = Price.create(1_000_000_000n).unwrap();
  const listing = ListingEntity.createNew(
    id, 'prop-1', 'seller-1', 'SALE', price, 100, 'agent-1',
  );
  listing.submitForReview();
  listing.clearDomainEvents();
  return listing;
}

describe('RejectListingHandler', () => {
  let handler: RejectListingHandler;
  let mockListingRepo: { [K in keyof IListingRepository]: ReturnType<typeof vi.fn> };
  let mockEventBus: { publish: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockListingRepo = {
      findById: vi.fn(),
      findByIdWithProperty: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
      search: vi.fn(),
      findByStatus: vi.fn(),
      findBySellerId: vi.fn(),
    };

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

    handler = new RejectListingHandler(
      mockListingRepo as any,
      mockEventBus as any,
    );
  });

  it('rejects a pending listing successfully', async () => {
    const listing = createPendingListing();
    mockListingRepo.findById.mockResolvedValue(listing);
    mockListingRepo.update.mockResolvedValue(undefined);

    const command = new RejectListingCommand('listing-1', 'admin-1', 'Vi phạm chính sách');
    const result = await handler.execute(command);

    expect(result.status).toBe('REJECTED');
    expect(result.listingId).toBe('listing-1');
    expect(result.message).toBe('Listing đã bị từ chối');
    expect(mockListingRepo.update).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalled();
  });

  it('throws NotFoundException when listing does not exist', async () => {
    mockListingRepo.findById.mockResolvedValue(null);

    const command = new RejectListingCommand('nonexistent', 'admin-1', 'Lý do');

    await expect(handler.execute(command)).rejects.toThrow('Listing không tồn tại');
  });

  it('throws ValidationException when listing is not pending review', async () => {
    const price = Price.create(500_000_000n).unwrap();
    const listing = ListingEntity.createNew(
      'listing-1', 'prop-1', 'seller-1', 'SALE', price, 80,
    );
    listing.clearDomainEvents();
    mockListingRepo.findById.mockResolvedValue(listing);

    const command = new RejectListingCommand('listing-1', 'admin-1', 'Lý do');

    await expect(handler.execute(command)).rejects.toThrow(/trạng thái/);
  });

  it('passes rejection reason to listing.reject()', async () => {
    const listing = createPendingListing();
    const rejectSpy = vi.spyOn(listing, 'reject');
    mockListingRepo.findById.mockResolvedValue(listing);
    mockListingRepo.update.mockResolvedValue(undefined);

    const command = new RejectListingCommand('listing-1', 'admin-1', 'Nội dung không phù hợp');
    await handler.execute(command);

    expect(rejectSpy).toHaveBeenCalledWith('Nội dung không phù hợp');
  });

  it('publishes ListingRejectedEvent with correct data', async () => {
    const listing = createPendingListing('listing-99');
    mockListingRepo.findById.mockResolvedValue(listing);
    mockListingRepo.update.mockResolvedValue(undefined);

    const command = new RejectListingCommand('listing-99', 'admin-42', 'Spam');
    await handler.execute(command);

    expect(mockEventBus.publish).toHaveBeenCalledWith(
      expect.objectContaining({
        aggregateId: 'listing-99',
        adminId: 'admin-42',
        reason: 'Spam',
      }),
    );
  });
});
