import { describe, it, expect, vi, beforeEach } from 'vitest';

const { mockFind, mockFindOne, mockAggregate, mockSave } = vi.hoisted(() => ({
  mockFind: vi.fn(),
  mockFindOne: vi.fn(),
  mockAggregate: vi.fn(),
  mockSave: vi.fn(),
}));

vi.mock('../../../src/schemas/medicine-price.schema.js', () => {
  const chain = () => ({
    sort: vi.fn().mockReturnThis(),
    limit: vi.fn().mockReturnThis(),
    lean: vi.fn().mockReturnThis(),
    exec: mockFind,
  });
  const findOneChain = () => ({
    sort: vi.fn().mockReturnThis(),
    lean: vi.fn().mockReturnThis(),
    exec: mockFindOne,
  });
  const aggregateChain = () => ({ exec: mockAggregate });

  class FakeModel {
    data: unknown;
    constructor(data: unknown) {
      this.data = data;
    }
    save = mockSave;
    toObject() {
      return this.data;
    }
    static find = vi.fn(() => chain());
    static findOne = vi.fn(() => findOneChain());
    static aggregate = vi.fn(() => aggregateChain());
  }
  return { MedicinePriceModel: FakeModel };
});

import { MedicinePricesRepository } from '../../../src/modules/medicine-prices/medicine-prices.repository.js';

describe(MedicinePricesRepository.name, () => {
  let repo: MedicinePricesRepository;

  beforeEach(() => {
    vi.clearAllMocks();
    repo = new MedicinePricesRepository();
  });

  describe('create', () => {
    it('saves and returns price record', async () => {
      const data = {
        householdId: 'hh1',
        medicineProductId: 'mp-1',
        medicineProductBrand: 'Tylenol',
        medicineId: 'med-1',
        medicineName: 'Acetaminophen',
        storeId: 'st-1',
        storeName: 'Walgreens',
        price: 10,
        currency: 'USD',
        quantity: 100,
        unit: 'tablet',
        pricePerUnit: 0.1,
        date: new Date(),
        isInsurancePrice: false,
        createdBy: 'user-1',
      };
      mockSave.mockImplementation(function (this: { toObject: () => unknown }) {
        return Promise.resolve(this);
      });

      const result = await repo.create(data as never);

      expect(mockSave).toHaveBeenCalled();
      expect(result).toBeTruthy();
    });
  });

  describe('findByMedicine', () => {
    it('returns paginated items', async () => {
      const items = [{ _id: 'pr-1', medicineId: 'med-1' }];
      mockFind.mockResolvedValue(items);

      const result = await repo.findByMedicine('hh1', 'med-1', { limit: 20 });

      expect(result.data).toEqual(items);
      expect(result.pagination.hasMore).toBe(false);
      expect(result.pagination.cursor).toBeNull();
    });

    it('sets hasMore when more items exist', async () => {
      const items = [{ _id: 'pr-1' }, { _id: 'pr-2' }, { _id: 'pr-3' }];
      mockFind.mockResolvedValue(items);

      const result = await repo.findByMedicine('hh1', 'med-1', { limit: 2 });

      expect(result.data).toHaveLength(2);
      expect(result.pagination.hasMore).toBe(true);
      expect(result.pagination.cursor).toBeTruthy();
    });

    it('handles cursor', async () => {
      mockFind.mockResolvedValue([]);

      const cursor = Buffer.from('pr-1').toString('base64');
      const result = await repo.findByMedicine('hh1', 'med-1', { cursor, limit: 20 });

      expect(result.pagination.hasMore).toBe(false);
    });

    it('returns null cursor when no data', async () => {
      mockFind.mockResolvedValue([]);

      const result = await repo.findByMedicine('hh1', 'med-1', { limit: 20 });

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

    it('applies storeId filter', async () => {
      mockFind.mockResolvedValue([]);

      await repo.findByMedicine('hh1', 'med-1', { storeId: 'st-1', limit: 20 });

      expect(mockFind).toHaveBeenCalled();
    });

    it('applies startDate-only filter', async () => {
      mockFind.mockResolvedValue([]);

      await repo.findByMedicine('hh1', 'med-1', {
        startDate: '2026-01-01T00:00:00.000Z',
        limit: 20,
      });

      expect(mockFind).toHaveBeenCalled();
    });

    it('applies endDate-only filter', async () => {
      mockFind.mockResolvedValue([]);

      await repo.findByMedicine('hh1', 'med-1', {
        endDate: '2026-12-31T00:00:00.000Z',
        limit: 20,
      });

      expect(mockFind).toHaveBeenCalled();
    });
  });

  describe('compareStores', () => {
    it('returns store comparison results', async () => {
      const rows = [
        {
          _id: 'st-1',
          storeName: 'Walgreens',
          latestPrice: 10,
          latestPricePerUnit: 0.1,
          currency: 'USD',
          date: new Date(),
          isInsurancePrice: false,
        },
      ];
      mockAggregate.mockResolvedValue(rows);

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

      expect(result).toHaveLength(1);
      expect(result[0].storeId).toBe('st-1');
      expect(result[0].storeName).toBe('Walgreens');
    });

    it('returns empty array when no records', async () => {
      mockAggregate.mockResolvedValue([]);

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

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

  describe('getLatestForMedicine', () => {
    it('returns latest record', async () => {
      const record = { _id: 'pr-1', pricePerUnit: 0.1 };
      mockFindOne.mockResolvedValue(record);

      const result = await repo.getLatestForMedicine('hh1', 'med-1');

      expect(result).toEqual(record);
    });

    it('filters by storeId when provided', async () => {
      mockFindOne.mockResolvedValue(null);

      const result = await repo.getLatestForMedicine('hh1', 'med-1', 'st-1');

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

    it('returns null when not found', async () => {
      mockFindOne.mockResolvedValue(null);

      expect(await repo.getLatestForMedicine('hh1', 'med-1')).toBeNull();
    });
  });

  describe('getAnalytics', () => {
    it('returns analytics object with all fields', async () => {
      mockAggregate.mockResolvedValue([]);

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

      expect(result).toHaveProperty('spendingOverTime');
      expect(result).toHaveProperty('topBySpending');
      expect(result).toHaveProperty('spendingByStore');
      expect(result).toHaveProperty('priceAlerts');
    });

    it('uses quarter date format', async () => {
      mockAggregate.mockResolvedValue([]);

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

      expect(result).toHaveProperty('spendingOverTime');
    });

    it('uses year date format', async () => {
      mockAggregate.mockResolvedValue([]);

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

      expect(result).toHaveProperty('spendingOverTime');
    });

    it('handles non-empty analytics results', async () => {
      mockAggregate
        .mockResolvedValueOnce([{ period: '2026-01', total: 50 }])
        .mockResolvedValueOnce([
          {
            medicineId: 'med-1',
            medicineName: 'Acetaminophen',
            totalSpent: 50,
            avgPricePerUnit: 0.1,
          },
        ])
        .mockResolvedValueOnce([
          { storeId: 'st-1', storeName: 'Walgreens', totalSpent: 50, purchaseCount: 5 },
        ])
        .mockResolvedValueOnce([]);

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

      expect(result.spendingOverTime).toHaveLength(1);
      expect(result.topBySpending).toHaveLength(1);
      expect(result.spendingByStore).toHaveLength(1);
      expect(result.priceAlerts).toHaveLength(0);
    });
  });
});
