import { type INeighborhoodScoreService, type NeighborhoodScoreResult } from '../../domain/services/neighborhood-score.service';
import { type CacheService } from '@modules/shared/infrastructure/cache.service';
import { GetNeighborhoodScoreHandler } from '../queries/get-neighborhood-score/get-neighborhood-score.handler';
import { GetNeighborhoodScoreQuery } from '../queries/get-neighborhood-score/get-neighborhood-score.query';

const sampleScore: NeighborhoodScoreResult = {
  district: 'Quận 1',
  city: 'Hồ Chí Minh',
  educationScore: 8,
  healthcareScore: 7,
  transportScore: 9,
  shoppingScore: 6,
  greeneryScore: 5,
  safetyScore: 4,
  totalScore: 68.5,
  poiCounts: { education: 12, healthcare: 5, transport: 10, shopping: 6, greenery: 3, safety: 2 },
  calculatedAt: new Date(),
};

describe('GetNeighborhoodScoreHandler', () => {
  let handler: GetNeighborhoodScoreHandler;
  let mockService: { [K in keyof INeighborhoodScoreService]: ReturnType<typeof vi.fn> };
  let mockCache: { getOrSet: ReturnType<typeof vi.fn> };

  beforeEach(() => {
    mockService = {
      getScore: vi.fn(),
      calculateAndSave: vi.fn(),
    };
    // Bypass cache: call the loader directly
    mockCache = {
      getOrSet: vi.fn((_key: string, loader: () => Promise<unknown>) => loader()),
    };
    handler = new GetNeighborhoodScoreHandler(
      mockService as any,
      mockCache as unknown as CacheService,
    );
  });

  it('returns cached score when available', async () => {
    mockService.getScore.mockResolvedValue(sampleScore);

    const result = await handler.execute(new GetNeighborhoodScoreQuery('Quận 1', 'Hồ Chí Minh'));

    expect(result).toEqual(sampleScore);
    expect(mockService.getScore).toHaveBeenCalledWith('Quận 1', 'Hồ Chí Minh');
    expect(mockService.calculateAndSave).not.toHaveBeenCalled();
  });

  it('calculates and saves score when no cached score exists', async () => {
    mockService.getScore.mockResolvedValue(null);
    mockService.calculateAndSave.mockResolvedValue(sampleScore);

    const result = await handler.execute(new GetNeighborhoodScoreQuery('Quận 2', 'Hồ Chí Minh'));

    expect(result).toEqual(sampleScore);
    expect(mockService.getScore).toHaveBeenCalledWith('Quận 2', 'Hồ Chí Minh');
    expect(mockService.calculateAndSave).toHaveBeenCalledWith('Quận 2', 'Hồ Chí Minh');
  });

  it('uses CacheService.getOrSet with 24h TTL', async () => {
    mockService.getScore.mockResolvedValue(sampleScore);

    await handler.execute(new GetNeighborhoodScoreQuery('Quận 1', 'Hồ Chí Minh'));

    expect(mockCache.getOrSet).toHaveBeenCalledWith(
      expect.stringContaining('neighborhood_score'),
      expect.any(Function),
      86400,
      'neighborhood-score',
    );
  });
});
