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

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

vi.mock('../../../src/schemas/refill-list.schema.js', () => {
  const chain = () => ({
    sort: vi.fn().mockReturnThis(),
    limit: vi.fn().mockReturnThis(),
    lean: vi.fn().mockReturnThis(),
    exec: mockFind,
  });
  const findOneChain = () => ({ lean: vi.fn().mockReturnThis(), exec: mockFindOne });
  const updateChain = () => ({ exec: mockFindOneAndUpdate });

  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 findOneAndUpdate = vi.fn(() => updateChain());
  }
  return { RefillListModel: FakeModel };
});

import { RefillsRepository } from '../../../src/modules/refills/refills.repository.js';

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

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

  describe('create', () => {
    it('saves and returns refill list', async () => {
      const data = {
        householdId: 'hh1',
        name: 'Monthly Refills',
        status: 'active',
        createdBy: 'user-1',
        items: [],
      };
      mockSave.mockImplementation(function (this: { toObject: () => unknown }) {
        return Promise.resolve(this);
      });

      const result = await repo.create(data);

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

  describe('findByHousehold', () => {
    it('returns paginated lists', async () => {
      const lists = [{ _id: 'rl-1', name: 'Monthly Refills' }];
      mockFind.mockResolvedValue(lists);

      const result = await repo.findByHousehold('hh1', { limit: 20 });

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

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

      const result = await repo.findByHousehold('hh1', { limit: 2 });

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

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

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

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

    it('filters by status', async () => {
      mockFind.mockResolvedValue([]);

      await repo.findByHousehold('hh1', { status: 'active' as never, limit: 20 });

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

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

      const result = await repo.findByHousehold('hh1', { limit: 20 });

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

  describe('findById', () => {
    it('returns list when found', async () => {
      const list = { _id: 'rl-1', name: 'Monthly Refills' };
      mockFindOne.mockResolvedValue(list);

      const result = await repo.findById('rl-1', 'hh1');

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

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

      expect(await repo.findById('missing', 'hh1')).toBeNull();
    });
  });

  describe('update', () => {
    it('updates and returns list', async () => {
      const updated = { _id: 'rl-1', name: 'Updated' };
      mockFindOneAndUpdate.mockResolvedValue(updated);

      const result = await repo.update('rl-1', 'hh1', { name: 'Updated' });

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

    it('updates status field', async () => {
      mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', status: 'shopping' });

      const result = await repo.update('rl-1', 'hh1', { status: 'shopping' as never });

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

    it('updates preferredStoreId field', async () => {
      mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', preferredStoreId: 'st-1' });

      const result = await repo.update('rl-1', 'hh1', { preferredStoreId: 'st-1' });

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

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

      expect(await repo.update('missing', 'hh1', {})).toBeNull();
    });
  });

  describe('updateItem', () => {
    it('updates item and returns list', async () => {
      const updated = { _id: 'rl-1', items: [{ _id: 'item-1', checked: true }] };
      mockFindOneAndUpdate.mockResolvedValue(updated);

      const result = await repo.updateItem('rl-1', 'hh1', 'item-1', { checked: true });

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

    it('updates actualPrice, storeId, and notes', async () => {
      mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', items: [] });

      await repo.updateItem('rl-1', 'hh1', 'item-1', {
        actualPrice: 9.99,
        storeId: 'st-1',
        notes: 'picked up at CVS',
      });

      expect(mockFindOneAndUpdate).toHaveBeenCalled();
    });

    it('includes checkedAt when provided', async () => {
      mockFindOneAndUpdate.mockResolvedValue({ _id: 'rl-1', items: [] });

      const checkedAt = new Date();
      await repo.updateItem('rl-1', 'hh1', 'item-1', { checked: true, checkedAt });

      expect(mockFindOneAndUpdate).toHaveBeenCalled();
    });
  });

  describe('markItemsAddedToCabinet', () => {
    it('marks items and returns list', async () => {
      const updated = { _id: 'rl-1', items: [] };
      mockFindOneAndUpdate.mockResolvedValue(updated);

      const result = await repo.markItemsAddedToCabinet('rl-1', 'hh1', ['item-1', 'item-2']);

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