import { NotFoundException } from '@modules/shared';
import type { IIndustrialListingRepository } from '../../domain/repositories/industrial-listing.repository';
import type { IIndustrialParkRepository } from '../../domain/repositories/industrial-park.repository';
import type { TypesenseIndustrialService } from '../../infrastructure/services/typesense-industrial.service';
import { CreateIndustrialListingHandler } from '../commands/create-industrial-listing/create-industrial-listing.handler';
import { CreateIndustrialListingCommand } from '../commands/create-industrial-listing/create-industrial-listing.command';

describe('CreateIndustrialListingHandler', () => {
  let handler: CreateIndustrialListingHandler;
  let mockRepo: { [K in keyof IIndustrialListingRepository]: ReturnType<typeof vi.fn> };
  let mockParkRepo: { [K in keyof IIndustrialParkRepository]: ReturnType<typeof vi.fn> };
  let mockTypesense: Partial<{ [K in keyof TypesenseIndustrialService]: ReturnType<typeof vi.fn> }>;

  const baseCmd = new CreateIndustrialListingCommand(
    'park-1',
    'seller-1',
    null,
    'READY_BUILT_FACTORY',
    'FACTORY_LEASE',
    'Nhà xưởng 2000m² KCN Bình Dương',
    'Nhà xưởng mới xây, sẵn sàng sử dụng',
    2000,
    12,
    5,
    null,
    4,
    null,
    false,
    true,
    200,
    5.5,
    'USD/m²/month',
    null,
    0.8,
    3,
    5,
    20,
    null,
    new Date('2025-01-01'),
    500,
    100,
    null,
  );

  beforeEach(() => {
    mockRepo = {
      findById: vi.fn(),
      findDetailById: vi.fn(),
      save: vi.fn().mockResolvedValue(undefined),
      update: vi.fn(),
      search: vi.fn(),
    };
    mockParkRepo = {
      findById: vi.fn(),
      findBySlug: vi.fn(),
      findDetailBySlug: vi.fn(),
      findDetailById: vi.fn(),
      save: vi.fn(),
      update: vi.fn(),
      search: vi.fn(),
      compareParks: vi.fn(),
      getStats: vi.fn(),
      getMarketData: vi.fn(),
    };
    mockTypesense = {
      indexListing: vi.fn().mockResolvedValue(undefined),
      indexPark: vi.fn(),
      deleteListing: vi.fn(),
    };
    handler = new CreateIndustrialListingHandler(
      mockRepo as any,
      mockParkRepo as any,
      mockTypesense as any,
    );
  });

  it('creates a listing when park exists', async () => {
    mockParkRepo.findById.mockResolvedValue({ id: 'park-1', name: 'Test Park' });

    const result = await handler.execute(baseCmd);

    expect(result).toHaveProperty('id');
    expect(mockParkRepo.findById).toHaveBeenCalledWith('park-1');
    expect(mockRepo.save).toHaveBeenCalledTimes(1);
    expect(mockTypesense.indexListing).toHaveBeenCalledWith(result.id);
  });

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

    await expect(handler.execute(baseCmd)).rejects.toThrow(NotFoundException);
    expect(mockRepo.save).not.toHaveBeenCalled();
  });

  it('creates listing with DRAFT status', async () => {
    mockParkRepo.findById.mockResolvedValue({ id: 'park-1' });

    await handler.execute(baseCmd);

    const savedEntity = mockRepo.save.mock.calls[0]![0];
    expect(savedEntity.status).toBe('DRAFT');
  });

  it('initializes viewCount and inquiryCount to zero', async () => {
    mockParkRepo.findById.mockResolvedValue({ id: 'park-1' });

    await handler.execute(baseCmd);

    const savedEntity = mockRepo.save.mock.calls[0]![0];
    expect(savedEntity.viewCount).toBe(0);
    expect(savedEntity.inquiryCount).toBe(0);
  });
});
