import { ValidationException } from '@modules/shared';
import type { IIndustrialParkRepository, IndustrialParkDetailData } from '../../domain/repositories/industrial-park.repository';
import { GetIndustrialParkHandler } from '../queries/get-industrial-park/get-industrial-park.handler';
import { GetIndustrialParkQuery } from '../queries/get-industrial-park/get-industrial-park.query';
import { ListIndustrialParksHandler } from '../queries/list-industrial-parks/list-industrial-parks.handler';
import { ListIndustrialParksQuery } from '../queries/list-industrial-parks/list-industrial-parks.query';
import { CompareIndustrialParksHandler } from '../queries/compare-industrial-parks/compare-industrial-parks.handler';
import { CompareIndustrialParksQuery } from '../queries/compare-industrial-parks/compare-industrial-parks.query';

const makeMockRepo = (): { [K in keyof IIndustrialParkRepository]: ReturnType<typeof vi.fn> } => ({
  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(),
});

const sampleDetail: IndustrialParkDetailData = {
  id: 'park-1',
  name: 'KCN Bình Dương',
  nameEn: 'Binh Duong IP',
  slug: 'kcn-binh-duong',
  developer: 'Becamex',
  operator: null,
  status: 'OPERATIONAL',
  latitude: 11.05,
  longitude: 106.67,
  address: '123 Đại lộ BD',
  district: 'Thủ Dầu Một',
  province: 'Bình Dương',
  region: 'SOUTH',
  totalAreaHa: 500,
  leasableAreaHa: 400,
  occupancyRate: 72,
  remainingAreaHa: 112,
  tenantCount: 45,
  establishedYear: 2005,
  landRentUsdM2Year: 120,
  rbfRentUsdM2Month: 5.5,
  rbwRentUsdM2Month: 4.8,
  managementFeeUsd: 0.8,
  infrastructure: null,
  connectivity: null,
  incentives: null,
  targetIndustries: ['Electronics'],
  existingTenants: null,
  certifications: null,
  media: null,
  documents: null,
  description: null,
  descriptionEn: null,
  isVerified: true,
  listingCount: 3,
  createdAt: new Date('2024-01-01'),
  updatedAt: new Date('2024-06-01'),
};

describe('GetIndustrialParkHandler', () => {
  let handler: GetIndustrialParkHandler;
  let mockRepo: ReturnType<typeof makeMockRepo>;

  beforeEach(() => {
    mockRepo = makeMockRepo();
    handler = new GetIndustrialParkHandler(mockRepo as any);
  });

  it('returns detail by slug first', async () => {
    mockRepo.findDetailBySlug.mockResolvedValue(sampleDetail);

    const result = await handler.execute(new GetIndustrialParkQuery('kcn-binh-duong'));

    expect(result).toEqual(sampleDetail);
    expect(mockRepo.findDetailBySlug).toHaveBeenCalledWith('kcn-binh-duong');
    expect(mockRepo.findDetailById).not.toHaveBeenCalled();
  });

  it('falls back to findDetailById if slug not found', async () => {
    mockRepo.findDetailBySlug.mockResolvedValue(null);
    mockRepo.findDetailById.mockResolvedValue(sampleDetail);

    const result = await handler.execute(new GetIndustrialParkQuery('park-1'));

    expect(result).toEqual(sampleDetail);
    expect(mockRepo.findDetailById).toHaveBeenCalledWith('park-1');
  });

  it('returns null when park not found by slug or id', async () => {
    mockRepo.findDetailBySlug.mockResolvedValue(null);
    mockRepo.findDetailById.mockResolvedValue(null);

    const result = await handler.execute(new GetIndustrialParkQuery('nonexistent'));

    expect(result).toBeNull();
  });
});

describe('ListIndustrialParksHandler', () => {
  let handler: ListIndustrialParksHandler;
  let mockRepo: ReturnType<typeof makeMockRepo>;

  beforeEach(() => {
    mockRepo = makeMockRepo();
    handler = new ListIndustrialParksHandler(mockRepo as any);
  });

  it('delegates search to repository with correct params', async () => {
    const paginatedResult = { data: [], total: 0, page: 1, limit: 20, totalPages: 0 };
    mockRepo.search.mockResolvedValue(paginatedResult);

    const query = new ListIndustrialParksQuery('electronics', 'Bình Dương', 'SOUTH', 'OPERATIONAL', 100, 150, 'Electronics', 2, 10);
    const result = await handler.execute(query);

    expect(result).toEqual(paginatedResult);
    expect(mockRepo.search).toHaveBeenCalledWith({
      query: 'electronics',
      province: 'Bình Dương',
      region: 'SOUTH',
      status: 'OPERATIONAL',
      minAreaHa: 100,
      maxRentUsdM2: 150,
      targetIndustry: 'Electronics',
      page: 2,
      limit: 10,
    });
  });

  it('uses default page and limit', async () => {
    mockRepo.search.mockResolvedValue({ data: [], total: 0, page: 1, limit: 20, totalPages: 0 });

    await handler.execute(new ListIndustrialParksQuery());

    expect(mockRepo.search).toHaveBeenCalledWith(
      expect.objectContaining({ page: 1, limit: 20 }),
    );
  });
});

describe('CompareIndustrialParksHandler', () => {
  let handler: CompareIndustrialParksHandler;
  let mockRepo: ReturnType<typeof makeMockRepo>;

  beforeEach(() => {
    mockRepo = makeMockRepo();
    handler = new CompareIndustrialParksHandler(mockRepo as any);
  });

  it('returns comparison data for valid park ids', async () => {
    mockRepo.compareParks.mockResolvedValue([sampleDetail, { ...sampleDetail, id: 'park-2' }]);

    const result = await handler.execute(new CompareIndustrialParksQuery(['park-1', 'park-2']));

    expect(result).toHaveLength(2);
    expect(mockRepo.compareParks).toHaveBeenCalledWith(['park-1', 'park-2']);
  });

  it('throws ValidationException for fewer than 2 ids', async () => {
    await expect(
      handler.execute(new CompareIndustrialParksQuery(['park-1'])),
    ).rejects.toThrow(ValidationException);
  });

  it('throws ValidationException for more than 5 ids', async () => {
    await expect(
      handler.execute(new CompareIndustrialParksQuery(['a', 'b', 'c', 'd', 'e', 'f'])),
    ).rejects.toThrow(ValidationException);
  });
});
