import { describe, it, expect, vi, beforeEach } from 'vitest';
import { MedicinePricesService } from '../../../src/modules/medicine-prices/medicine-prices.service.js';

describe(MedicinePricesService.name, () => {
  const mockPricesRepo = {
    create: vi.fn(),
    findByMedicine: vi.fn(),
    compareStores: vi.fn(),
    getLatestForMedicine: vi.fn(),
    getAnalytics: vi.fn(),
  };
  const mockProductsRepo = {
    findById: vi.fn(),
  };
  const mockStoresRepo = {
    findById: vi.fn(),
  };

  let service: MedicinePricesService;

  beforeEach(() => {
    vi.clearAllMocks();
    service = new MedicinePricesService({
      medicinePricesRepository: mockPricesRepo as never,
      medicineProductsRepository: mockProductsRepo as never,
      storesRepository: mockStoresRepo as never,
    });
  });

  describe('recordPrice', () => {
    const validInput = {
      medicineProductId: 'mp-1',
      medicineId: 'med-1',
      storeId: 'st-1',
      price: 10,
      currency: 'USD',
      quantity: 100,
      unit: 'tablet' as never,
      isInsurancePrice: false,
    };

    it('creates price record with computed pricePerUnit', async () => {
      mockProductsRepo.findById.mockResolvedValue({
        medicineName: 'Acetaminophen',
        brand: 'Tylenol',
      });
      mockStoresRepo.findById.mockResolvedValue({ name: 'Walgreens' });
      const record = { _id: 'pr-1', pricePerUnit: 0.1 };
      mockPricesRepo.create.mockResolvedValue(record);

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

      expect(result).toEqual(record);
      expect(mockPricesRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({
          pricePerUnit: 0.1,
          medicineName: 'Acetaminophen',
          storeName: 'Walgreens',
        }),
      );
    });

    it('uses medicineName as brand fallback when brand is not set', async () => {
      mockProductsRepo.findById.mockResolvedValue({ medicineName: 'Generic', brand: undefined });
      mockStoresRepo.findById.mockResolvedValue({ name: 'CVS' });
      mockPricesRepo.create.mockResolvedValue({ _id: 'pr-1' });

      await service.recordPrice(validInput, 'hh1', 'user-1');

      expect(mockPricesRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({ medicineProductBrand: 'Generic' }),
      );
    });

    it('uses provided date when given', async () => {
      mockProductsRepo.findById.mockResolvedValue({
        medicineName: 'Acetaminophen',
        brand: 'Tylenol',
      });
      mockStoresRepo.findById.mockResolvedValue({ name: 'Walgreens' });
      mockPricesRepo.create.mockResolvedValue({ _id: 'pr-1' });

      await service.recordPrice(
        { ...validInput, date: '2026-01-15T00:00:00.000Z' },
        'hh1',
        'user-1',
      );

      expect(mockPricesRepo.create).toHaveBeenCalledWith(
        expect.objectContaining({ date: new Date('2026-01-15T00:00:00.000Z') }),
      );
    });

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

      await expect(service.recordPrice(validInput, 'hh1', 'user-1')).rejects.toThrow(
        'Medicine product not found',
      );
    });

    it('throws NotFoundError when store not found', async () => {
      mockProductsRepo.findById.mockResolvedValue({
        medicineName: 'Acetaminophen',
        brand: 'Tylenol',
      });
      mockStoresRepo.findById.mockResolvedValue(null);

      await expect(service.recordPrice(validInput, 'hh1', 'user-1')).rejects.toThrow(
        'Store not found',
      );
    });
  });

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

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

      expect(response).toEqual(result);
      expect(mockPricesRepo.findByMedicine).toHaveBeenCalledWith('hh1', 'med-1', { limit: 20 });
    });
  });

  describe('compareStores', () => {
    it('delegates to repository', async () => {
      const comparisons = [{ storeId: 'st-1', storeName: 'Walgreens', latestPrice: 10 }];
      mockPricesRepo.compareStores.mockResolvedValue(comparisons);

      const result = await service.compareStores('hh1', 'med-1');

      expect(result).toEqual(comparisons);
      expect(mockPricesRepo.compareStores).toHaveBeenCalledWith('hh1', 'med-1');
    });
  });

  describe('estimatePrice', () => {
    it('returns pricePerUnit of latest record', async () => {
      mockPricesRepo.getLatestForMedicine.mockResolvedValue({ pricePerUnit: 0.15 });

      const result = await service.estimatePrice('hh1', 'med-1');

      expect(result).toBe(0.15);
    });

    it('returns null when no records', async () => {
      mockPricesRepo.getLatestForMedicine.mockResolvedValue(null);

      const result = await service.estimatePrice('hh1', 'med-1');

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

    it('filters by storeId when provided', async () => {
      mockPricesRepo.getLatestForMedicine.mockResolvedValue({ pricePerUnit: 0.2 });

      await service.estimatePrice('hh1', 'med-1', 'st-1');

      expect(mockPricesRepo.getLatestForMedicine).toHaveBeenCalledWith('hh1', 'med-1', 'st-1');
    });
  });

  describe('getAnalytics', () => {
    it('delegates to repository', async () => {
      const analytics = {
        spendingOverTime: [],
        topBySpending: [],
        spendingByStore: [],
        priceAlerts: [],
      };
      mockPricesRepo.getAnalytics.mockResolvedValue(analytics);

      const result = await service.getAnalytics('hh1', { period: 'month' });

      expect(result).toEqual(analytics);
      expect(mockPricesRepo.getAnalytics).toHaveBeenCalledWith('hh1', { period: 'month' });
    });
  });
});
