import { NotFoundException } from '@nestjs/common';
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
import {
  type IMarketIndexRepository,
  type WardHeatmapDataPoint,
  type ListingVolumeWardResult,
} from '../../domain/repositories/market-index.repository';
import { GetHeatmapHandler } from '../queries/get-heatmap/get-heatmap.handler';
import { GetHeatmapQuery } from '../queries/get-heatmap/get-heatmap.query';
import { GetListingVolumeWardHandler } from '../queries/get-listing-volume-ward/get-listing-volume-ward.handler';
import { GetListingVolumeWardQuery } from '../queries/get-listing-volume-ward/get-listing-volume-ward.query';

// Shared mock helpers
function makeRepo(): { [K in keyof IMarketIndexRepository]: ReturnType<typeof vi.fn> } {
  return {
    findById: vi.fn(),
    findByKey: vi.fn(),
    save: vi.fn(),
    update: vi.fn(),
    getMarketReport: vi.fn(),
    getHeatmap: vi.fn(),
    getHeatmapWard: vi.fn(),
    getListingVolumeByWard: vi.fn(),
    getPriceTrend: vi.fn(),
    getDistrictStats: vi.fn(),
    getMarketHistory: vi.fn(),
  };
}

function makeCache(): CacheService {
  return {
    getOrSet: vi.fn((_key: string, loader: () => Promise<unknown>) => loader()),
  } as unknown as CacheService;
}

function makeLogger() {
  return { log: vi.fn(), error: vi.fn(), warn: vi.fn() } as any;
}

// ---------------------------------------------------------------------------
// GetHeatmapHandler — ward level
// ---------------------------------------------------------------------------
describe('GetHeatmapHandler — level=ward', () => {
  let handler: GetHeatmapHandler;
  let mockRepo: ReturnType<typeof makeRepo>;

  beforeEach(() => {
    mockRepo = makeRepo();
    handler = new GetHeatmapHandler(mockRepo as any, makeCache(), makeLogger());
  });

  it('delegates to getHeatmapWard and returns level=ward in the dto', async () => {
    const wardPoints: WardHeatmapDataPoint[] = [
      { ward: 'Phường Bến Nghé', district: 'Quận 1', city: 'Hồ Chí Minh', avgPriceM2: 130_000_000, totalListings: 42, medianPrice: '7000000000' },
      { ward: 'Phường Cầu Kho', district: 'Quận 1', city: 'Hồ Chí Minh', avgPriceM2: 100_000_000, totalListings: 18, medianPrice: '5500000000' },
    ];
    mockRepo.getHeatmapWard.mockResolvedValue(wardPoints);

    const query = new GetHeatmapQuery('Hồ Chí Minh', '2026-Q1', 'ward', 'Quận 1');
    const result = await handler.execute(query);

    expect(result.level).toBe('ward');
    expect(result.city).toBe('Hồ Chí Minh');
    expect(result.period).toBe('2026-Q1');
    expect(result.dataPoints).toEqual(wardPoints);
    expect(mockRepo.getHeatmapWard).toHaveBeenCalledWith('Hồ Chí Minh', '2026-Q1', 'Quận 1');
    expect(mockRepo.getHeatmap).not.toHaveBeenCalled();
  });

  it('returns level=district when level is omitted (default)', async () => {
    mockRepo.getHeatmap.mockResolvedValue([]);

    const query = new GetHeatmapQuery('Hồ Chí Minh', '2026-Q1');
    const result = await handler.execute(query);

    expect(result.level).toBe('district');
    expect(mockRepo.getHeatmap).toHaveBeenCalled();
    expect(mockRepo.getHeatmapWard).not.toHaveBeenCalled();
  });

  it('returns empty dataPoints for ward level when no data', async () => {
    mockRepo.getHeatmapWard.mockResolvedValue([]);

    const query = new GetHeatmapQuery('Đà Nẵng', '2025-Q4', 'ward');
    const result = await handler.execute(query);

    expect(result.level).toBe('ward');
    expect(result.dataPoints).toEqual([]);
  });
});

// ---------------------------------------------------------------------------
// GetListingVolumeWardHandler
// ---------------------------------------------------------------------------
describe('GetListingVolumeWardHandler', () => {
  let handler: GetListingVolumeWardHandler;
  let mockRepo: ReturnType<typeof makeRepo>;

  beforeEach(() => {
    mockRepo = makeRepo();
    handler = new GetListingVolumeWardHandler(mockRepo as any, makeCache(), makeLogger());
  });

  it('returns listing volume for a ward and period', async () => {
    const volume: ListingVolumeWardResult = {
      ward: 'Phường Bến Nghé',
      district: 'Quận 1',
      city: 'Hồ Chí Minh',
      period: '2026-Q1',
      totalListings: 58,
      avgPriceM2: 128_000_000,
      medianPrice: '6800000000',
    };
    mockRepo.getListingVolumeByWard.mockResolvedValue(volume);

    const query = new GetListingVolumeWardQuery('Phường Bến Nghé', '2026-Q1');
    const result = await handler.execute(query);

    expect(result).toEqual(volume);
    expect(mockRepo.getListingVolumeByWard).toHaveBeenCalledWith('Phường Bến Nghé', '2026-Q1');
  });

  it('throws NotFoundException when no data found for the ward/period', async () => {
    mockRepo.getListingVolumeByWard.mockResolvedValue(null);

    const query = new GetListingVolumeWardQuery('Phường Không Tồn Tại', '2020-Q1');

    await expect(handler.execute(query)).rejects.toThrow(NotFoundException);
  });

  it('supports monthly period format', async () => {
    const volume: ListingVolumeWardResult = {
      ward: 'Phường 12',
      district: 'Quận Bình Thạnh',
      city: 'Hồ Chí Minh',
      period: '2026-03',
      totalListings: 22,
      avgPriceM2: 65_000_000,
      medianPrice: '3200000000',
    };
    mockRepo.getListingVolumeByWard.mockResolvedValue(volume);

    const query = new GetListingVolumeWardQuery('Phường 12', '2026-03');
    const result = await handler.execute(query);

    expect(result.period).toBe('2026-03');
    expect(result.totalListings).toBe(22);
  });
});
