import { CachePrefix, CacheService } from '@modules/shared/infrastructure/cache.service';
import { ListingStatusChangedHandler } from '../event-handlers/listing-status-changed.handler';

describe('ListingStatusChangedHandler', () => {
  let handler: ListingStatusChangedHandler;
  let mockIndexer: { indexListing: ReturnType<typeof vi.fn>; removeListing: ReturnType<typeof vi.fn> };
  let mockCache: {
    invalidate: ReturnType<typeof vi.fn>;
    invalidateByPrefix: ReturnType<typeof vi.fn>;
  };
  let mockLogger: { log: ReturnType<typeof vi.fn>; warn: ReturnType<typeof vi.fn>; error: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockIndexer = {
      indexListing: vi.fn().mockResolvedValue(undefined),
      removeListing: vi.fn().mockResolvedValue(undefined),
    };
    mockCache = {
      invalidate: vi.fn().mockResolvedValue(undefined),
      invalidateByPrefix: vi.fn().mockResolvedValue(undefined),
    };
    mockLogger = {
      log: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    };
    handler = new ListingStatusChangedHandler(mockIndexer as any, mockCache as any, mockLogger as any);
  });

  it('removes listing from index when status changed to REJECTED', async () => {
    await handler.handle({
      aggregateId: 'listing-1',
      propertyId: 'prop-1',
      previousStatus: 'ACTIVE',
      newStatus: 'REJECTED',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockIndexer.removeListing).toHaveBeenCalledWith('listing-1');
  });

  it('removes listing from index when status changed to EXPIRED', async () => {
    await handler.handle({
      aggregateId: 'listing-2',
      propertyId: 'prop-2',
      previousStatus: 'ACTIVE',
      newStatus: 'EXPIRED',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockIndexer.removeListing).toHaveBeenCalledWith('listing-2');
  });

  it('removes listing from index when status changed to SOLD', async () => {
    await handler.handle({
      aggregateId: 'listing-3',
      propertyId: 'prop-3',
      previousStatus: 'ACTIVE',
      newStatus: 'SOLD',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockIndexer.removeListing).toHaveBeenCalledWith('listing-3');
  });

  it('removes listing from index when status changed to RENTED', async () => {
    await handler.handle({
      aggregateId: 'listing-4',
      propertyId: 'prop-4',
      previousStatus: 'ACTIVE',
      newStatus: 'RENTED',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockIndexer.removeListing).toHaveBeenCalledWith('listing-4');
  });

  it('does NOT remove listing from index when status changed to ACTIVE', async () => {
    await handler.handle({
      aggregateId: 'listing-5',
      propertyId: 'prop-5',
      previousStatus: 'PENDING_REVIEW',
      newStatus: 'ACTIVE',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockIndexer.removeListing).not.toHaveBeenCalled();
  });

  it('invalidates listing cache, search cache, and geo search cache on any status change', async () => {
    await handler.handle({
      aggregateId: 'listing-6',
      propertyId: 'prop-6',
      previousStatus: 'DRAFT',
      newStatus: 'ACTIVE',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockCache.invalidate).toHaveBeenCalledWith(
      CacheService.buildKey(CachePrefix.LISTING, 'listing-6'),
    );
    expect(mockCache.invalidateByPrefix).toHaveBeenCalledWith(CachePrefix.SEARCH);
    expect(mockCache.invalidateByPrefix).toHaveBeenCalledWith(CachePrefix.GEO_SEARCH);
  });

  it('logs the status transition', async () => {
    await handler.handle({
      aggregateId: 'listing-7',
      propertyId: 'prop-7',
      previousStatus: 'ACTIVE',
      newStatus: 'SOLD',
      eventName: 'listing.status_changed',
      occurredAt: new Date(),
    } as any);

    expect(mockLogger.log).toHaveBeenCalledWith(
      expect.stringContaining('ACTIVE'),
      expect.any(String),
    );
    expect(mockLogger.log).toHaveBeenCalledWith(
      expect.stringContaining('SOLD'),
      expect.any(String),
    );
  });
});
