import { ListingEntity } from '@modules/listings/domain/entities/listing.entity';
import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository';
import { ModerationService } from '@modules/listings/domain/services/moderation.service';
import { Price } from '@modules/listings/domain/value-objects/price.vo';
import { UpdateListingStatusCommand } from '../commands/update-listing-status/update-listing-status.command';
import { UpdateListingStatusHandler } from '../commands/update-listing-status/update-listing-status.handler';

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

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

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

    mockEventBus = { publish: vi.fn() };
    mockCache = {
      invalidate: vi.fn().mockResolvedValue(undefined),
      invalidateByPrefix: vi.fn().mockResolvedValue(undefined),
    };

    handler = new UpdateListingStatusHandler(
      mockListingRepo as any,
      mockEventBus as any,
      mockCache as any,
      new ModerationService(),
    );
  });

  it('approves a pending listing via ACTIVE status', async () => {
    const listing = createListing('listing-1', 'PENDING_REVIEW');
    mockListingRepo.findById.mockResolvedValue(listing);

    const command = new UpdateListingStatusCommand('listing-1', 'ACTIVE', 'admin-1');
    const result = await handler.execute(command);

    expect(result.status).toBe('ACTIVE');
    expect(mockListingRepo.update).toHaveBeenCalledTimes(1);
    expect(mockEventBus.publish).toHaveBeenCalled();
  });

  it('rejects a listing with moderation notes', async () => {
    const listing = createListing('listing-1', 'PENDING_REVIEW');
    mockListingRepo.findById.mockResolvedValue(listing);

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

    expect(result.status).toBe('REJECTED');
  });

  it('transitions active listing to SOLD', async () => {
    const listing = createListing('listing-1', 'ACTIVE');
    mockListingRepo.findById.mockResolvedValue(listing);

    const command = new UpdateListingStatusCommand('listing-1', 'SOLD', 'seller-1');
    const result = await handler.execute(command);

    expect(result.status).toBe('SOLD');
  });

  it('throws NotFoundException for non-existent listing', async () => {
    mockListingRepo.findById.mockResolvedValue(null);

    const command = new UpdateListingStatusCommand('nonexistent', 'ACTIVE', 'admin-1');

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

  it('throws ValidationException for invalid status transition', async () => {
    const listing = createListing('listing-1', 'DRAFT');
    mockListingRepo.findById.mockResolvedValue(listing);

    const command = new UpdateListingStatusCommand('listing-1', 'SOLD', 'seller-1');

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