import { type PrismaService } from '@modules/shared/infrastructure/prisma.service';
import { MarketIndexEntity } from '../../domain/entities/market-index.entity';
import { PrismaMarketIndexRepository } from '../repositories/prisma-market-index.repository';

const makeRawRecord = (overrides: Record<string, unknown> = {}) => ({
  id: 'idx-1',
  district: 'Quận 1',
  city: 'Hồ Chí Minh',
  propertyType: 'APARTMENT' as const,
  period: '2026-Q1',
  medianPrice: BigInt(5000000000),
  avgPriceM2: 50,
  totalListings: 100,
  daysOnMarket: 30,
  inventoryLevel: 5,
  absorptionRate: 0.8,
  yoyChange: 0.1,
  createdAt: new Date(),
  updatedAt: new Date(),
  ...overrides,
});

describe('PrismaMarketIndexRepository', () => {
  let repo: PrismaMarketIndexRepository;
  let mockPrisma: {
    marketIndex: {
      findUnique: ReturnType<typeof vi.fn>;
      findMany: ReturnType<typeof vi.fn>;
      create: ReturnType<typeof vi.fn>;
      update: ReturnType<typeof vi.fn>;
    };
  };

  beforeEach(() => {
    mockPrisma = {
      marketIndex: {
        findUnique: vi.fn(),
        findMany: vi.fn(),
        create: vi.fn(),
        update: vi.fn(),
      },
    };
    repo = new PrismaMarketIndexRepository(mockPrisma as unknown as PrismaService);
  });

  it('findById returns entity when found', async () => {
    const raw = makeRawRecord();
    mockPrisma.marketIndex.findUnique.mockResolvedValue(raw);

    const result = await repo.findById('idx-1');

    expect(result).toBeInstanceOf(MarketIndexEntity);
    expect(result?.id).toBe('idx-1');
    expect(result?.district).toBe('Quận 1');
    expect(result?.medianPrice).toBe(BigInt(5000000000));
    expect(mockPrisma.marketIndex.findUnique).toHaveBeenCalledWith({ where: { id: 'idx-1' } });
  });

  it('findById returns null when not found', async () => {
    mockPrisma.marketIndex.findUnique.mockResolvedValue(null);

    const result = await repo.findById('nonexistent');

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

  it('findByKey returns entity when found', async () => {
    const raw = makeRawRecord();
    mockPrisma.marketIndex.findUnique.mockResolvedValue(raw);

    const result = await repo.findByKey('Quận 1', 'Hồ Chí Minh', 'APARTMENT', '2026-Q1');

    expect(result).toBeInstanceOf(MarketIndexEntity);
    expect(result?.district).toBe('Quận 1');
    expect(mockPrisma.marketIndex.findUnique).toHaveBeenCalledWith({
      where: {
        district_city_propertyType_period: {
          district: 'Quận 1',
          city: 'Hồ Chí Minh',
          propertyType: 'APARTMENT',
          period: '2026-Q1',
        },
      },
    });
  });

  it('save calls prisma.marketIndex.create with correct data', async () => {
    mockPrisma.marketIndex.create.mockResolvedValue(undefined);

    const entity = MarketIndexEntity.createNew(
      'idx-new',
      'Quận 2',
      'Hồ Chí Minh',
      'APARTMENT',
      '2026-Q1',
      BigInt(5000000000),
      50,
      100,
      30,
      5,
      0.8,
      0.1,
    );

    await repo.save(entity);

    expect(mockPrisma.marketIndex.create).toHaveBeenCalledWith({
      data: {
        id: 'idx-new',
        district: 'Quận 2',
        city: 'Hồ Chí Minh',
        propertyType: 'APARTMENT',
        period: '2026-Q1',
        medianPrice: BigInt(5000000000),
        avgPriceM2: 50,
        totalListings: 100,
        daysOnMarket: 30,
        inventoryLevel: 5,
        absorptionRate: 0.8,
        yoyChange: 0.1,
      },
    });
  });

  it('update calls prisma.marketIndex.update with correct data', async () => {
    mockPrisma.marketIndex.update.mockResolvedValue(undefined);

    const entity = MarketIndexEntity.createNew(
      'idx-upd',
      'Quận 3',
      'Hồ Chí Minh',
      'VILLA',
      '2026-Q1',
      BigInt(7000000000),
      90,
      60,
      20,
      2,
      0.5,
      0.05,
    );

    await repo.update(entity);

    expect(mockPrisma.marketIndex.update).toHaveBeenCalledWith({
      where: { id: 'idx-upd' },
      data: {
        medianPrice: BigInt(7000000000),
        avgPriceM2: 90,
        totalListings: 60,
        daysOnMarket: 20,
        inventoryLevel: 2,
        absorptionRate: 0.5,
        yoyChange: 0.05,
      },
    });
  });

  it('getMarketReport returns mapped results', async () => {
    const raw = makeRawRecord();
    mockPrisma.marketIndex.findMany.mockResolvedValue([raw]);

    const results = await repo.getMarketReport('Hồ Chí Minh', '2026-Q1');

    expect(results).toHaveLength(1);
    expect(results[0]).toMatchObject({
      district: 'Quận 1',
      city: 'Hồ Chí Minh',
      propertyType: 'APARTMENT',
      period: '2026-Q1',
      medianPrice: '5000000000',
      avgPriceM2: 50,
      totalListings: 100,
      daysOnMarket: 30,
    });
  });

  it('getMarketReport filters by propertyType when provided', async () => {
    mockPrisma.marketIndex.findMany.mockResolvedValue([]);

    await repo.getMarketReport('Hồ Chí Minh', '2026-Q1', 'VILLA');

    expect(mockPrisma.marketIndex.findMany).toHaveBeenCalledWith({
      where: { city: 'Hồ Chí Minh', period: '2026-Q1', propertyType: 'VILLA' },
      orderBy: { district: 'asc' },
    });
  });

  it('getHeatmap aggregates by district correctly', async () => {
    const records = [
      makeRawRecord({ district: 'Quận 1', avgPriceM2: 80, totalListings: 100, medianPrice: BigInt(5000000000) }),
      makeRawRecord({ district: 'Quận 1', avgPriceM2: 60, totalListings: 50, medianPrice: BigInt(4000000000) }),
      makeRawRecord({ district: 'Quận 2', avgPriceM2: 70, totalListings: 80, medianPrice: BigInt(4500000000) }),
    ];
    mockPrisma.marketIndex.findMany.mockResolvedValue(records);

    const results = await repo.getHeatmap('Hồ Chí Minh', '2026-Q1');

    const quan1 = results.find((r) => r.district === 'Quận 1');
    const quan2 = results.find((r) => r.district === 'Quận 2');

    expect(results).toHaveLength(2);
    expect(quan1).toBeDefined();
    expect(quan1?.avgPriceM2).toBe(70); // (80 + 60) / 2
    expect(quan1?.totalListings).toBe(150); // 100 + 50
    expect(quan2).toBeDefined();
    expect(quan2?.avgPriceM2).toBe(70);
  });

  it('getPriceTrend returns mapped results', async () => {
    const raw = makeRawRecord({ period: '2025-Q4' });
    mockPrisma.marketIndex.findMany.mockResolvedValue([raw]);

    const results = await repo.getPriceTrend('Quận 1', 'Hồ Chí Minh', 'APARTMENT', ['2025-Q4', '2026-Q1']);

    expect(results).toHaveLength(1);
    expect(results[0]).toMatchObject({
      period: '2025-Q4',
      medianPrice: '5000000000',
      avgPriceM2: 50,
      totalListings: 100,
    });
    expect(mockPrisma.marketIndex.findMany).toHaveBeenCalledWith({
      where: {
        district: 'Quận 1',
        city: 'Hồ Chí Minh',
        propertyType: 'APARTMENT',
        period: { in: ['2025-Q4', '2026-Q1'] },
      },
      orderBy: { period: 'asc' },
    });
  });

  it('getDistrictStats returns mapped results', async () => {
    const raw = makeRawRecord();
    mockPrisma.marketIndex.findMany.mockResolvedValue([raw]);

    const results = await repo.getDistrictStats('Hồ Chí Minh', '2026-Q1');

    expect(results).toHaveLength(1);
    expect(results[0]).toMatchObject({
      district: 'Quận 1',
      city: 'Hồ Chí Minh',
      propertyType: 'APARTMENT',
      medianPrice: '5000000000',
      avgPriceM2: 50,
      totalListings: 100,
      daysOnMarket: 30,
      inventoryLevel: 5,
      absorptionRate: 0.8,
      yoyChange: 0.1,
    });
  });
});
