import { describe, it, expect, vi, beforeEach } from 'vitest';
import { RegimensService } from '../../../src/modules/regimens/regimens.service.js';
import { DosageFrequency } from '@meshitrack/shared';

describe(RegimensService.name, () => {
  const mockRegimensRepo = {
    findByHousehold: vi.fn(),
    findById: vi.fn(),
    findActiveByUser: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    softDelete: vi.fn(),
  };

  const mockMedicinesRepo = {
    findById: vi.fn(),
    findByHousehold: vi.fn(),
    findDuplicate: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    softDelete: vi.fn(),
  };

  const mockCabinetRepo = {
    findByHousehold: vi.fn(),
    findById: vi.fn(),
    getAggregateSummary: vi.fn(),
    create: vi.fn(),
    update: vi.fn(),
    adjustQuantity: vi.fn(),
    findExpiringSoon: vi.fn(),
    softDelete: vi.fn(),
    countByMedicineId: vi.fn(),
    discard: vi.fn(),
    findActiveByMedicineForFEFO: vi.fn(),
  };

  const mockCabinetEventsService = {
    logEvent: vi.fn(),
    logEvents: vi.fn(),
    listEvents: vi.fn(),
    getEventsByItem: vi.fn(),
    getSpendingSummary: vi.fn(),
    getAvgUnitPrices: vi.fn(),
  };

  let service: RegimensService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new RegimensService({
      regimensRepository: mockRegimensRepo as never,
      medicinesRepository: mockMedicinesRepo as never,
      cabinetRepository: mockCabinetRepo as never,
      cabinetEventsService: mockCabinetEventsService as never,
    });
  });

  describe('list', () => {
    it('delegates to repository', async () => {
      const result = { data: [], pagination: { cursor: null, hasMore: false } };
      mockRegimensRepo.findByHousehold.mockResolvedValue(result);

      const response = await service.list('hh1', 'user-1', { limit: 20 });

      expect(response).toEqual(result);
      expect(mockRegimensRepo.findByHousehold).toHaveBeenCalledWith('hh1', 'user-1', { limit: 20 });
    });
  });

  describe('getById', () => {
    it('returns regimen when found', async () => {
      const regimen = { _id: 'reg-1', name: 'Morning' };
      mockRegimensRepo.findById.mockResolvedValue(regimen);

      const result = await service.getById('reg-1', 'hh1', 'user-1');

      expect(result).toEqual(regimen);
      expect(mockRegimensRepo.findById).toHaveBeenCalledWith('reg-1', 'hh1', 'user-1');
    });

    it('throws NotFoundError when not found', async () => {
      mockRegimensRepo.findById.mockResolvedValue(null);

      await expect(service.getById('reg-missing', 'hh1', 'user-1')).rejects.toThrow(
        'Regimen not found',
      );
    });
  });

  describe('create', () => {
    const createInput = {
      name: 'Morning Routine',
      isActive: true,
      medications: [
        {
          medicineId: 'med-1',
          dosage: 1,
          dosageUnit: 'tablet' as const,
          frequency: DosageFrequency.DAILY,
        },
      ],
    };

    it('creates regimen with denormalized medications', async () => {
      mockMedicinesRepo.findById.mockResolvedValue({
        _id: 'med-1',
        name: 'Metformin',
        strength: 500,
        strengthUnit: 'mg',
        form: 'tablet',
      });
      const created = {
        _id: 'reg-1',
        ...createInput,
        medications: [{ medicineId: 'med-1', medicineName: 'Metformin' }],
      };
      mockRegimensRepo.create.mockResolvedValue(created);

      const result = await service.create(createInput, 'hh1', 'user-1');

      expect(result).toEqual(created);
      expect(mockRegimensRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          name: 'Morning Routine',
          isActive: true,
          medications: expect.arrayContaining([
            expect.objectContaining({
              medicineId: 'med-1',
              medicineName: 'Metformin',
              medicineStrength: 500,
              medicineStrengthUnit: 'mg',
              medicineForm: 'tablet',
              dosage: 1,
              dosageUnit: 'tablet',
              frequency: DosageFrequency.DAILY,
            }),
          ]),
        }),
        'hh1',
        'user-1',
        'user-1',
      );
    });

    it('preserves optional medication fields', async () => {
      mockMedicinesRepo.findById.mockResolvedValue({
        _id: 'med-1',
        name: 'Metformin',
        strength: 500,
        strengthUnit: 'mg',
        form: 'tablet',
      });
      mockRegimensRepo.create.mockResolvedValue({ _id: 'reg-1' });

      await service.create(
        {
          name: 'Morning',
          isActive: true,
          medications: [
            {
              medicineId: 'med-1',
              dosage: 2,
              dosageUnit: 'tablet' as const,
              frequency: DosageFrequency.CUSTOM,
              customFrequencyPerDay: 4,
              timeOfDay: 'morning' as never,
              instructions: 'Take with food',
            },
          ],
        },
        'hh1',
        'user-1',
      );

      expect(mockRegimensRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          medications: expect.arrayContaining([
            expect.objectContaining({
              customFrequencyPerDay: 4,
              timeOfDay: 'morning',
              instructions: 'Take with food',
            }),
          ]),
        }),
        'hh1',
        'user-1',
        'user-1',
      );
    });

    it('throws NotFoundError when medicine not found', async () => {
      mockMedicinesRepo.findById.mockResolvedValue(null);

      await expect(service.create(createInput, 'hh1', 'user-1')).rejects.toThrow(
        'Medicine not found: med-1',
      );
    });

    it('denormalizes multiple medications', async () => {
      mockMedicinesRepo.findById
        .mockResolvedValueOnce({
          _id: 'med-1',
          name: 'Metformin',
          strength: 500,
          strengthUnit: 'mg',
          form: 'tablet',
        })
        .mockResolvedValueOnce({
          _id: 'med-2',
          name: 'Aspirin',
          strength: 100,
          strengthUnit: 'mg',
          form: 'tablet',
        });
      mockRegimensRepo.create.mockResolvedValue({ _id: 'reg-1' });

      await service.create(
        {
          name: 'Full Routine',
          isActive: true,
          medications: [
            {
              medicineId: 'med-1',
              dosage: 1,
              dosageUnit: 'tablet' as const,
              frequency: DosageFrequency.DAILY,
            },
            {
              medicineId: 'med-2',
              dosage: 1,
              dosageUnit: 'tablet' as const,
              frequency: DosageFrequency.TWICE_DAILY,
            },
          ],
        },
        'hh1',
        'user-1',
      );

      expect(mockRegimensRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          medications: expect.arrayContaining([
            expect.objectContaining({ medicineId: 'med-1', medicineName: 'Metformin' }),
            expect.objectContaining({ medicineId: 'med-2', medicineName: 'Aspirin' }),
          ]),
        }),
        'hh1',
        'user-1',
        'user-1',
      );
    });
  });

  describe('update', () => {
    it('updates name only', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1', name: 'Morning' });
      const updated = { _id: 'reg-1', name: 'Evening' };
      mockRegimensRepo.update.mockResolvedValue(updated);

      const result = await service.update('reg-1', 'hh1', 'user-1', { name: 'Evening' });

      expect(result).toEqual(updated);
      expect(mockRegimensRepo.update).toHaveBeenCalledWith('reg-1', 'hh1', 'user-1', {
        name: 'Evening',
      });
    });

    it('updates isActive only', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1', isActive: true });
      mockRegimensRepo.update.mockResolvedValue({ _id: 'reg-1', isActive: false });

      await service.update('reg-1', 'hh1', 'user-1', { isActive: false });

      expect(mockRegimensRepo.update).toHaveBeenCalledWith('reg-1', 'hh1', 'user-1', {
        isActive: false,
      });
    });

    it('updates medications with denormalization', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1', name: 'Morning' });
      mockMedicinesRepo.findById.mockResolvedValue({
        _id: 'med-1',
        name: 'Metformin',
        strength: 500,
        strengthUnit: 'mg',
        form: 'tablet',
      });
      mockRegimensRepo.update.mockResolvedValue({ _id: 'reg-1' });

      await service.update('reg-1', 'hh1', 'user-1', {
        medications: [
          {
            medicineId: 'med-1',
            dosage: 2,
            dosageUnit: 'tablet' as const,
            frequency: DosageFrequency.DAILY,
          },
        ],
      });

      expect(mockRegimensRepo.update).toHaveBeenCalledWith(
        'reg-1',
        'hh1',
        'user-1',
        expect.objectContaining({
          medications: expect.arrayContaining([
            expect.objectContaining({ medicineName: 'Metformin' }),
          ]),
        }),
      );
    });

    it('throws NotFoundError when regimen not found on initial lookup', async () => {
      mockRegimensRepo.findById.mockResolvedValue(null);

      await expect(
        service.update('reg-missing', 'hh1', 'user-1', { name: 'Updated' }),
      ).rejects.toThrow('Regimen not found');
    });

    it('throws NotFoundError when update returns null', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1', name: 'Morning' });
      mockRegimensRepo.update.mockResolvedValue(null);

      await expect(service.update('reg-1', 'hh1', 'user-1', { name: 'Updated' })).rejects.toThrow(
        'Regimen not found',
      );
    });

    it('skips undefined fields in updateData', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1' });
      mockRegimensRepo.update.mockResolvedValue({ _id: 'reg-1' });

      await service.update('reg-1', 'hh1', 'user-1', {});

      expect(mockRegimensRepo.update).toHaveBeenCalledWith('reg-1', 'hh1', 'user-1', {});
    });

    it('throws NotFoundError when medicine in medications not found', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1' });
      mockMedicinesRepo.findById.mockResolvedValue(null);

      await expect(
        service.update('reg-1', 'hh1', 'user-1', {
          medications: [
            {
              medicineId: 'med-missing',
              dosage: 1,
              dosageUnit: 'tablet' as const,
              frequency: DosageFrequency.DAILY,
            },
          ],
        }),
      ).rejects.toThrow('Medicine not found: med-missing');
    });
  });

  describe('delete', () => {
    it('soft deletes regimen', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1' });
      mockRegimensRepo.softDelete.mockResolvedValue({ _id: 'reg-1', isDeleted: true });

      const result = await service.delete('reg-1', 'hh1', 'user-1');

      expect(result.isDeleted).toBe(true);
      expect(mockRegimensRepo.softDelete).toHaveBeenCalledWith('reg-1', 'hh1', 'user-1');
    });

    it('throws NotFoundError when regimen not found on initial lookup', async () => {
      mockRegimensRepo.findById.mockResolvedValue(null);

      await expect(service.delete('reg-missing', 'hh1', 'user-1')).rejects.toThrow(
        'Regimen not found',
      );
    });

    it('throws NotFoundError when softDelete returns null', async () => {
      mockRegimensRepo.findById.mockResolvedValue({ _id: 'reg-1' });
      mockRegimensRepo.softDelete.mockResolvedValue(null);

      await expect(service.delete('reg-1', 'hh1', 'user-1')).rejects.toThrow('Regimen not found');
    });
  });

  describe('getActiveByUser', () => {
    it('delegates to repository', async () => {
      const regimens = [{ _id: 'reg-1', isActive: true }];
      mockRegimensRepo.findActiveByUser.mockResolvedValue(regimens);

      const result = await service.getActiveByUser('hh1', 'user-1');

      expect(result).toEqual(regimens);
      expect(mockRegimensRepo.findActiveByUser).toHaveBeenCalledWith('hh1', 'user-1');
    });
  });

  describe('calculateBurnRates', () => {
    it('returns empty array when no active regimens', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([]);

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result).toEqual([]);
    });

    it('returns empty array when regimens have no medications', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([{ _id: 'reg-1', medications: [] }]);

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result).toEqual([]);
    });

    it('calculates burn rates for single medicine', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Metformin',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 30, earliestExpiry: new Date('2026-06-01T00:00:00.000Z') },
      ]);

      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(
        new Map([['med-1', { avgUnitPrice: 0.5, currency: 'USD' }]]),
      );

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result).toHaveLength(1);
      expect(result[0].medicineId).toBe('med-1');
      expect(result[0].medicineName).toBe('Metformin');
      expect(result[0].dailyConsumption).toBe(1);
      expect(result[0].totalInCabinet).toBe(30);
      expect(result[0].daysUntilEmpty).toBe(30);
      expect(result[0].earliestExpiry).toBe('2026-06-01T00:00:00.000Z');
      expect(result[0].avgUnitPrice).toBe(0.5);
      expect(result[0].projectedDailyCost).toBe(0.5);
      expect(result[0].projectedMonthlyCost).toBe(15);
      expect(result[0].projectedYearlyCost).toBe(182.5);
      expect(result[0].currency).toBe('USD');
    });

    it('sums daily consumption across multiple regimens', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Metformin',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
          ],
        },
        {
          _id: 'reg-2',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Metformin',
              dosage: 1,
              frequency: DosageFrequency.TWICE_DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 90, earliestExpiry: null },
      ]);

      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // 1*1 + 1*2 = 3 daily
      expect(result[0].dailyConsumption).toBe(3);
      expect(result[0].daysUntilEmpty).toBe(30); // 90 / 3 = 30
      expect(result[0].earliestExpiry).toBeNull();
      expect(result[0].avgUnitPrice).toBeNull();
      expect(result[0].projectedDailyCost).toBeNull();
      expect(result[0].projectedMonthlyCost).toBeNull();
      expect(result[0].projectedYearlyCost).toBeNull();
      expect(result[0].currency).toBeNull();
    });

    it('handles medicine not in cabinet stock', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Metformin',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result[0].totalInCabinet).toBe(0);
      expect(result[0].daysUntilEmpty).toBe(0); // Math.floor(0/1) = 0
    });

    it('excludes AS_NEEDED frequency from burn rate results', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Ibuprofen',
              dosage: 1,
              frequency: DosageFrequency.AS_NEEDED,
            },
          ],
        },
      ]);

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result).toEqual([]);
    });

    it('handles CUSTOM frequency with customFrequencyPerDay', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Custom Med',
              dosage: 2,
              frequency: DosageFrequency.CUSTOM,
              customFrequencyPerDay: 3,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 60, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // 2 * 3 = 6 daily
      expect(result[0].dailyConsumption).toBe(6);
      expect(result[0].daysUntilEmpty).toBe(10); // 60 / 6 = 10
    });

    it('excludes CUSTOM frequency without customFrequencyPerDay (zero consumption)', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Custom Med',
              dosage: 2,
              frequency: DosageFrequency.CUSTOM,
              // no customFrequencyPerDay -> 0 daily consumption -> excluded
            },
          ],
        },
      ]);

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result).toEqual([]);
    });

    it('sorts by daysUntilEmpty ascending (most urgent first, nulls last)', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Med A',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
            {
              medicineId: 'med-2',
              medicineName: 'Med B',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
            {
              medicineId: 'med-3',
              medicineName: 'Med C',
              dosage: 1,
              frequency: DosageFrequency.AS_NEEDED,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 30, earliestExpiry: null },
        { _id: 'med-2', totalQuantity: 10, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // AS_NEEDED (med-3) excluded; med-2: 10 days, med-1: 30 days
      expect(result).toHaveLength(2);
      expect(result[0].medicineId).toBe('med-2');
      expect(result[0].daysUntilEmpty).toBe(10);
      expect(result[1].medicineId).toBe('med-1');
      expect(result[1].daysUntilEmpty).toBe(30);
    });

    it('returns empty array when all medications are AS_NEEDED', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Med A',
              dosage: 1,
              frequency: DosageFrequency.AS_NEEDED,
            },
            {
              medicineId: 'med-2',
              medicineName: 'Med B',
              dosage: 1,
              frequency: DosageFrequency.AS_NEEDED,
            },
          ],
        },
      ]);

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result).toEqual([]);
    });

    it('excludes AS_NEEDED from results even when mixed with scheduled frequencies', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Med A',
              dosage: 1,
              frequency: DosageFrequency.AS_NEEDED,
            },
            {
              medicineId: 'med-2',
              medicineName: 'Med B',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
            {
              medicineId: 'med-3',
              medicineName: 'Med C',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-2', totalQuantity: 10, earliestExpiry: null },
        { _id: 'med-3', totalQuantity: 30, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // AS_NEEDED (med-1) excluded; med-2: 10 days, med-3: 30 days
      expect(result).toHaveLength(2);
      expect(result[0].medicineId).toBe('med-2');
      expect(result[0].daysUntilEmpty).toBe(10);
      expect(result[1].medicineId).toBe('med-3');
      expect(result[1].daysUntilEmpty).toBe(30);
    });

    it('handles WEEKLY frequency', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Weekly Med',
              dosage: 1,
              frequency: DosageFrequency.WEEKLY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 4, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // 1 * (1/7) ~= 0.1429 daily
      expect(result[0].dailyConsumption).toBeCloseTo(1 / 7);
      expect(result[0].daysUntilEmpty).toBe(28); // Math.floor(4 / (1/7)) = 28
    });

    it('handles EVERY_OTHER_DAY frequency', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'EOD Med',
              dosage: 1,
              frequency: DosageFrequency.EVERY_OTHER_DAY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 15, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // 1 * 0.5 = 0.5 daily
      expect(result[0].dailyConsumption).toBe(0.5);
      expect(result[0].daysUntilEmpty).toBe(30); // Math.floor(15 / 0.5) = 30
    });

    it('handles THREE_TIMES_DAILY frequency', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'TID Med',
              dosage: 1,
              frequency: DosageFrequency.THREE_TIMES_DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 90, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(new Map());

      const result = await service.calculateBurnRates('hh1', 'user-1');

      expect(result[0].dailyConsumption).toBe(3);
      expect(result[0].daysUntilEmpty).toBe(30); // 90 / 3 = 30
    });

    it('calculates projected costs correctly', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Expensive Med',
              dosage: 2,
              frequency: DosageFrequency.DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 60, earliestExpiry: null },
      ]);
      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(
        new Map([['med-1', { avgUnitPrice: 1.5, currency: 'EUR' }]]),
      );

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // dailyConsumption = 2, avgUnitPrice = 1.5
      expect(result[0].projectedDailyCost).toBe(3); // 1.5 * 2
      expect(result[0].projectedMonthlyCost).toBe(90); // 3 * 30
      expect(result[0].projectedYearlyCost).toBe(1095); // 3 * 365
      expect(result[0].currency).toBe('EUR');
    });

    it('handles multiple medicines with different stock and price data', async () => {
      mockRegimensRepo.findActiveByUser.mockResolvedValue([
        {
          _id: 'reg-1',
          medications: [
            {
              medicineId: 'med-1',
              medicineName: 'Med A',
              dosage: 1,
              frequency: DosageFrequency.DAILY,
            },
            {
              medicineId: 'med-2',
              medicineName: 'Med B',
              dosage: 1,
              frequency: DosageFrequency.TWICE_DAILY,
            },
          ],
        },
      ]);

      mockCabinetRepo.getAggregateSummary.mockResolvedValue([
        { _id: 'med-1', totalQuantity: 10, earliestExpiry: new Date('2026-03-01T00:00:00.000Z') },
        { _id: 'med-2', totalQuantity: 60, earliestExpiry: new Date('2026-12-01T00:00:00.000Z') },
      ]);

      mockCabinetEventsService.getAvgUnitPrices.mockResolvedValue(
        new Map([['med-1', { avgUnitPrice: 2.0, currency: 'USD' }]]),
      );

      const result = await service.calculateBurnRates('hh1', 'user-1');

      // med-1: 10 days until empty, med-2: 30 days
      expect(result[0].medicineId).toBe('med-1');
      expect(result[0].daysUntilEmpty).toBe(10);
      expect(result[0].avgUnitPrice).toBe(2.0);
      expect(result[1].medicineId).toBe('med-2');
      expect(result[1].daysUntilEmpty).toBe(30);
      expect(result[1].avgUnitPrice).toBeNull();
      expect(result[1].currency).toBeNull();
    });
  });
});
